Java实现LeetCode 题号:561 - 580

141 阅读4分钟

「这是我参与2022首次更文挑战的第6天,活动详情查看:2022首次更文挑战」。

LeetCode习题集 有些题可能直接略过了,整理一下之前刷leetcode

561. 数组拆分 I

给定长度为 2n 的数组, 你的任务是将这些数分成 n 对, 例如 (a1, b1), (a2, b2), ..., (an, bn) ,使得从1 到 n 的 min(ai, bi) 总和最大。

示例 1:

输入: [1,4,3,2]

输出: 4 解释: n 等于 2, 最大总和为 4 = min(1, 2) + min(3, 4). 提示:

n 是正整数,范围在 [1, 10000]. 数组中的元素范围在 [-10000, 10000].

PS:
    n个数对中找每个数对的最小值
        要想讲最小值和最大,也就是让最大值和最小,我们让a和比a大的最小的数组合,都按照这种规律就能找到最小值和最大的情况
        也就是给数组排个序,每两个挨着的数作为一个数对,把小的值加起来
        本解法用的是,把没两个数对的差算出来,然后用总和减去数对差除2就是结果
                    总和减数对差,就是每个数对就变成了两个相同的最小值,然后除2就是最小值
                    
        使用计数排序,对于数值范围小的数效率比较高
        searchFirst用来记录是数对的第几个数,第一次是true,然后再来一次就是false,不断循环
class Solution {
     public int arrayPairSum(int[] nums) {
        boolean[] flag = new boolean[20001];
        int sum = 0;
        for (int v: nums) {
            flag[v + 10000] = !flag[v + 10000];
            sum += v;
        }

        int loss = 0;
        boolean searchFirst = true;
        int first = 0;
        for (int i = 0; i <= 20000; i++) {
            if (flag[i]) {
                if (searchFirst)
                    first = i;
                else
                    loss += i - first;
                
                searchFirst = !searchFirst;
            }
        }
        return (sum - loss) / 2;

    }
}

563. 二叉树的坡度

给定一个二叉树,计算整个树的坡度。

一个树的节点的坡度定义即为,该节点左子树的结点之和和右子树结点之和的差的绝对值。空结点的的坡度是0。

整个树的坡度就是其所有节点的坡度之和。

示例:

输入: 1 /
2 3 输出: 1 解释: 结点的坡度 2 : 0 结点的坡度 3 : 0 结点的坡度 1 : |2-3| = 1 树的坡度 : 0 + 0 + 1 = 1 注意:

任何子树的结点的和不会超过32位整数的范围。 坡度的值不会超过32位整数的范围。

PS:
    其实就是遍历树,遍历树的同时,每次遍历到一个结点就把当前结点左子树和右子树只差记录一下,
    然后返回上一结点的时候,把当前树的左子树右子树跟结点之和返回给上一结点
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    private  int n = 0;
    public int findTilt(TreeNode root) {
        add(root);
        return n;
    }
    public int add(TreeNode treeNode){
        if(treeNode == null){
            return 0;
        }
        int left = add(treeNode.left);
        int right = add(treeNode.right);
        n += Math.abs(left-right);
        return treeNode.val+left+right;
    }
}

564. 寻找最近的回文数

给定一个整数 n ,你需要找到与它最近的回文数(不包括自身)。

“最近的”定义为两个整数差的绝对值最小。

示例 1:

输入: "123" 输出: "121" 注意:

n 是由字符串表示的正整数,其长度不超过18。 如果有多个结果,返回最小的那个。

PS:
	可以证明以下结论: 如果n 的前半部分是整数N,那么它的解一定是 以下三者之一:

N-1 和 N-1的回文组成的数字。
N 和 N的回文组成的数字
N+1 和 N+1 的回文组成的数字

class Solution {
     public String mirroring(String s) {
        String x = s.substring(0, (s.length()) / 2);
        return x + (s.length() % 2 == 1 ? s.charAt(s.length() / 2) : "") + new StringBuilder(x).reverse().toString();
    }
    public String nearestPalindromic(String n) {
        if (n.equals("1"))
            return "0";

        String a = mirroring(n);
        long diff1 = Long.MAX_VALUE;
        diff1 = Math.abs(Long.parseLong(n) - Long.parseLong(a));
        if (diff1 == 0)
            diff1 = Long.MAX_VALUE;

        StringBuilder s = new StringBuilder(n);
        int i = (s.length() - 1) / 2;
        while (i >= 0 && s.charAt(i) == '0') {
            s.replace(i, i + 1, "9");
            i--;
        }
        if (i == 0 && s.charAt(i) == '1') {
            s.delete(0, 1);
            int mid = (s.length() - 1) / 2;
            s.replace(mid, mid + 1, "9");
        } else
            s.replace(i, i + 1, "" + (char)(s.charAt(i) - 1));
        String b = mirroring(s.toString());
        long diff2 = Math.abs(Long.parseLong(n) - Long.parseLong(b));


        s = new StringBuilder(n);
        i = (s.length() - 1) / 2;
        while (i >= 0 && s.charAt(i) == '9') {
            s.replace(i, i + 1, "0");
            i--;
        }
        if (i < 0) {
            s.insert(0, "1");
        } else
            s.replace(i, i + 1, "" + (char)(s.charAt(i) + 1));
        String c = mirroring(s.toString());
        long diff3 = Math.abs(Long.parseLong(n) - Long.parseLong(c));

        if (diff2 <= diff1 && diff2 <= diff3)
            return b;
        if (diff1 <= diff3 && diff1 <= diff2)
            return a;
        else
            return c;
    }
 
}

565. 数组嵌套

索引从0开始长度为N的数组A,包含0到N - 1的所有整数。找到并返回最大的集合S,S[i] = {A[i], A[A[i]], A[A[A[i]]], ... }且遵守以下的规则。

假设选择索引为i的元素A[i]为S的第一个元素,S的下一个元素应该是A[A[i]],之后是A[A[A[i]]]... 以此类推,不断添加直到S出现重复的元素。

示例 1:

输入: A = [5,4,0,3,1,6,2] 输出: 4 解释: A[0] = 5, A[1] = 4, A[2] = 0, A[3] = 3, A[4] = 1, A[5] = 6, A[6] = 2.

其中一种最长的 S[K]: S[0] = {A[0], A[5], A[6], A[2]} = {5, 6, 2, 0} 注意:

N是[1, 20,000]之间的整数。 A中不含有重复的元素。 A中的元素大小在[0, N-1]之间。

PS:
    从每个位置都开始测试一下,记录最长的数组
    被用过的就可以标记为-1,因为一旦被用过,如果再次用的话还会陷入同样的圈子,所以直接标记为-1就可以了
class Solution {
    public int arrayNesting(int[] nums) {
        int max = 0;
        for (int i = 0; i < nums.length; i++) {
            int cnt = 0;
            for (int j = i; nums[j] != -1; ) {
                cnt++;
                int t = nums[j];
                nums[j] = -1;  
                j = t;
            }
            max = Math.max(max, cnt);
        }
        return max;
    }
}

566. 重塑矩阵

在MATLAB中,有一个非常有用的函数 reshape,它可以将一个矩阵重塑为另一个大小不同的新矩阵,但保留其原始数据。

给出一个由二维数组表示的矩阵,以及两个正整数r和c,分别表示想要的重构的矩阵的行数和列数。

重构后的矩阵需要将原始矩阵的所有元素以相同的行遍历顺序填充。

如果具有给定参数的reshape操作是可行且合理的,则输出新的重塑矩阵;否则,输出原始矩阵。

示例 1:

输入: nums = [[1,2], [3,4]] r = 1, c = 4 输出: [[1,2,3,4]] 解释: 行遍历nums的结果是 [1,2,3,4]。新的矩阵是 1 * 4 矩阵, 用之前的元素值一行一行填充新矩阵。 示例 2:

输入: nums = [[1,2], [3,4]] r = 2, c = 4 输出: [[1,2], [3,4]] 解释: 没有办法将 2 * 2 矩阵转化为 2 * 4 矩阵。 所以输出原矩阵。 注意:

给定矩阵的宽和高范围在 [1, 100]。 给定的 r 和 c 都是正数。

PS:
    如果新矩阵和旧矩阵元素数量不一样,是没办法组成新矩阵的
    直接暴力循环,旧矩阵按照行进行遍历,
        旧矩阵的当前行遍历完,就去下一行
        如果新矩阵当前行满了,就到新矩阵的下一行
class Solution {
    public int[][] matrixReshape(int[][] nums, int r, int c) {
 if (r * c != nums.length * nums[0].length)
			return nums;
		int[][] ans = new int[r][c];
		int row = 0, col = 0;
		for (int[] i : nums) {
			for (int j : i) {
				ans[row][(col++) % c] = j;
				if (col % c == 0) 
					row++;
			}
		}
		return ans;
    }
}

567. 字符串的排列

给定两个字符串 s1 和 s2,写一个函数来判断 s2 是否包含 s1 的排列。

换句话说,第一个字符串的排列之一是第二个字符串的子串。

示例1:

输入: s1 = "ab" s2 = "eidbaooo" 输出: True 解释: s2 包含 s1 的排列之一 ("ba").

示例2:

输入: s1= "ab" s2 = "eidboaoo" 输出: False

注意:

输入的字符串只包含小写字母 两个字符串的长度都在 [1, 10,000] 之间

PS:
    滑动窗口:
        因为判断的是s1的排列,我们比较的时候可以比较s1的各个字符的数量
        我们用s1的各个字符数量当作标准
        然后从s2最前面s1.length个字符开始,保证窗口内永远是s1.length个字符
        每次移动,把窗口左端的字符删除,把窗口右端的字符添加进窗口,
        每次移动到一个位置,更改后就进行匹配,直到最后,如果都没有匹配成功就返回false
        
class Solution {
     public boolean checkInclusion(String s1, String s2) {
        if(s2.length()<s1.length())return false;

        int[] book=new int[26];
        for (int i=0;i<s1.length();i++)
        {
            book[s1.charAt(i)-'a']++;
        }

        int[] book1=new int[26];
        for (int i=0;i<s1.length();i++)
        {
            book1[s2.charAt(i)-'a']++;
        }
        if(check(book,book1))return true;

        int left=0,right=s1.length();
        while (right<s2.length())
        {
            book1[s2.charAt(left++)-'a']--;
            book1[s2.charAt(right++)-'a']++;
            if(check(book,book1))return true;
        }

        return false;
    }

    private boolean check(int[] book, int[] book1) {
        for (int i=0;i<26;i++)
        {
            if(book[i]!=book1[i])return false;
        }
        return true;
    }
}

572. 另一个树的子树

给定两个非空二叉树 s 和 t,检验 s 中是否包含和 t 具有相同结构和节点值的子树。s 的一个子树包括 s 的一个节点和这个节点的所有子孙。s 也可以看做它自身的一棵子树。

示例 1: 给定的树 s:

     3
    / \
   4   5
  / \
 1   2

给定的树 t:

   4 
  / \
 1   2

返回 true,因为 t 与 s 的一个子树拥有相同的结构和节点值。

示例 2: 给定的树 s:

     3
    / \
   4   5
  / \
 1   2
    /
   0

给定的树 t:

   4
  / \
 1   2

返回 false。

PS:
    递归子树,每次递归到一个结点都进行匹配
        如果当前结点值和目标结点值相等,就标记一个flag为true,然后继续匹配左子节点和右子结点,
        如果不相等的话,就标记flag为false,然后把左子节点和目标树或者右子节点和目标树进行匹配
 
class Solution {
    public boolean isSubtree(TreeNode s, TreeNode t) {
        return isSame(s, t, false);
    }


    public boolean isSame(TreeNode s, TreeNode t, boolean flag){
        if(s==null && t==null) return true;
        else if(s!=null && t!=null){
            if(s.val == t.val){
                return (isSame(s.left, t.left, true) && isSame(s.right, t.right, true)) || isSame(s.left, t, false) || isSame(s.right, t, false);
            }else{
                if(flag) return false;
                else return isSame(s.left, t, false) || isSame(s.right, t, false);
            }
        }
        return false;
    }
}

575. 分糖果

给定一个偶数长度的数组,其中不同的数字代表着不同种类的糖果,每一个数字代表一个糖果。你需要把这些糖果平均分给一个弟弟和一个妹妹。返回妹妹可以获得的最大糖果的种类数。

示例 1:

输入: candies = [1,1,2,2,3,3] 输出: 3 解析: 一共有三种种类的糖果,每一种都有两个。 最优分配方案:妹妹获得[1,2,3],弟弟也获得[1,2,3]。这样使妹妹获得糖果的种类数最多。 示例 2 :

输入: candies = [1,1,2,3] 输出: 2 解析: 妹妹获得糖果[2,3],弟弟获得糖果[1,1],妹妹有两种不同的糖果,弟弟只有一种。这样使得妹妹可以获得的糖果种类数最多。 注意:

数组的长度为[2, 10,000],并且确定为偶数。 数组中数字的大小在范围[-100,000, 100,000]内。

PS:
    因为只返回种类数,而不需要返回或者的种类都有什么,
        所以直接比较种类数和总长度的一半,哪个小用哪个
class Solution {
      public int distributeCandies(int[] candies) {
        HashSet<Integer> set = new HashSet<Integer>();
	for(int candy : candies){
	    set.add(candy);
	}
	return Math.min(set.size(), candies.length/2);
    }
}

576. 出界的路径数

给定一个 m × n 的网格和一个球。球的起始坐标为 (i,j) ,你可以将球移到相邻的单元格内,或者往上、下、左、右四个方向上移动使球穿过网格边界。但是,你最多可以移动 N 次。找出可以将球移出边界的路径数量。答案可能非常大,返回 结果 mod 109 + 7 的值。

示例 1:

输入: m = 2, n = 2, N = 2, i = 0, j = 0 输出: 6 解释: 在这里插入图片描述 示例 2:

输入: m = 1, n = 3, N = 3, i = 0, j = 1 输出: 12 解释:

在这里插入图片描述

说明:

球一旦出界,就不能再被移动回网格内。 网格的长度和高度在 [1,50] 的范围内。 N 在 [0,50] 的范围内。

PS:
	DFS
            经典DFS,用一个数组记录,数组三个位置分别是,横坐标,纵坐标,和步数(第N步到达当前位置的次数)
            每到一个位置,先判断是不是超过界限了,然后从当前位置上下左右找,次数减1,
            把上下左右四个位置符合的值存到当前位置
            如果以前相同次数走过这个位置,直接返回这个位置的值,—— 剪枝操作

        
class Solution {
    private Integer[][][] cache;
    
    public int findPaths(int m, int n, int N, int i, int j) {
        cache = new Integer[m][n][N+1];
        return dfs(m,n,N,j,i);
    }
    
    private int dfs(int rows,int cols,int times,int x,int y) { 
        if (isOutOfBoundary(x,y,rows,cols)) {
            return 1;
        }
        if (0 == times) {
            return 0;
        } 
        if (null != cache[y][x][times]) {
            return cache[y][x][times];
        }
        int res = (((dfs(rows,cols,times-1,x+1,y) + dfs(rows,cols,times-1,x-1,y)) % 1000000007) + ((dfs(rows,cols,times-1,x,y+1) + dfs(rows,cols,times-1,x,y-1)) % 1000000007)) % 1000000007;
        cache[y][x][times] = res;
        return res;
    }
    
    private boolean isOutOfBoundary(int x,int y,int rows,int cols) {
        return x < 0 || x >= cols || y < 0 || y >= rows;
    }
}
PS:
    	动态规划
            首先循环步数次,创建一个新的数组,用来保存此次经过的点,然后从 0,0 循环到 m,n
            每个位置循环四个方向的移动,如果超过了界限,就把当前位置记录一次
            如果没超过界限,就把前一步的位置结果加上当前位置保存到当前位置
class Solution {
    public int findPaths(int m, int n, int N, int i, int j) {
 if(N <= 0) return 0;
        int mod = 1000000007;
        int ret = 0;
        int[][] dp = new int[m][n]; // 保存第k步的结果
        int[][] dirs = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
        
        for(int k = 1; k <= N; ++k) {
            int[][] temp = new int[m][n]; // 保存第k-1步的结果
            for(int x = 0; x < m; ++x) {
                for(int y = 0; y < n; ++y) {
                    for(int[] dir : dirs) {
                        int nx = x + dir[0];
                        int ny = y + dir[1];
                        if(nx < 0 || nx >= m || ny < 0 || ny >= n)
                            temp[x][y] += 1;
                        else
                            temp[x][y] = (dp[nx][ny] + temp[x][y]) % mod;
                    }
                }
            }
            dp = temp;
        }
        
        return dp[i][j];
    }
}