Leetcode打卡

2020/4/18

数组专题 中等难度

1

238. 除自身以外数组的乘积

给你一个长度为 n 的整数数组 nums,其中 n > 1,返回输出数组 output ,其中 output[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。

示例:

输入: [1,2,3,4]
输出: [24,12,8,6]

class Solution {
    public int[] productExceptSelf(int[] nums) {
        int[] ans = new int[nums.length];
        int mul=nums[0];
        for(int i=1;i<nums.length;i++){
            ans[i]=mul;
            mul=mul*nums[i];
        }
        ans[0]=1;
        mul=nums[nums.length-1];
        for(int i=nums.length-2;i>=0;i--){
            ans[i]*=mul;
            mul=mul*nums[i];
        }
        return ans;
    }
}

2

56. 合并区间

给出一个区间的集合,请合并所有重叠的区间。

示例 1:

输入: [[1,3],[2,6],[8,10],[15,18]]
输出: [[1,6],[8,10],[15,18]]
解释: 区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
示例 2:

输入: [[1,4],[4,5]]
输出: [[1,5]]
解释: 区间 [1,4] 和 [4,5] 可被视为重叠区间。

思路:讲数组按照第一个位子升序排列,按照第二个位子降序排列,然后循环,若当前的右短点小于循环时刻的左短点时,划分出一次,接下来循环节点作为当前节点继续循环下去。

class Solution {
    public int[][] merge(int[][] intervals) {
        if(intervals.length<2)
        return intervals;
        Arrays.sort(intervals,(o1,o2)->{
            return o1[0]==o2[0]?o2[1]-o1[1]:o1[0]-o2[0];
        });
        List<Integer> list = new ArrayList<>();
        int left =intervals[0][0];
        int right = intervals[0][1];
        for(int i=1;i<intervals.length;i++){
            if(right>=intervals[i][0]){
                right=Math.max(right,intervals[i][1]);
            }else{
                list.add(left);
                list.add(right);
                left=intervals[i][0];
                right=intervals[i][1];
            }
        }
               list.add(left);
                list.add(right);
        int[][] ans = new int[list.size()/2][2];
        for(int i=0;i<list.size()/2;i++){
            ans[i][0]=list.get(i*2);
            ans[i][1]=list.get(i*2+1);
        }
        return ans;
    }
}

3

287. 寻找重复数

给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。

示例 1:

输入: [1,3,4,2,2]
输出: 2
示例 2:

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

class Solution {
    public int findDuplicate(int[] nums) {
        int[] arr= new int[nums.length];
        for(int i=0;i<nums.length;i++){
        arr[nums[i]]++;
        if(arr[nums[i]]>1)
        return nums[i];
        }
        return 0;
        
    }
}

4

442. 数组中重复的数据

给定一个整数数组 a,其中1 ≤ a[i] ≤ n (n为数组长度), 其中有些元素出现两次而其他元素出现一次。

找到所有出现两次的元素。

你可以不用到任何额外空间并在O(n)时间复杂度内解决这个问题吗?

示例:

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

输出:
[2,3]

class Solution {
    public List<Integer> findDuplicates(int[] nums) {
        int[] arr = new int[nums.length+1];
        List<Integer> ans = new ArrayList<>();
        for(int i=0;i<nums.length;i++){
            arr[nums[i]]++;
            if(arr[nums[i]]>1)
            ans.add(nums[i]);
        }
        return ans;
    }
}

5

289. 生命游戏

根据 百度百科 ,生命游戏,简称为生命,是英国数学家约翰·何顿·康威在 1970 年发明的细胞自动机。

给定一个包含 m × n 个格子的面板,每一个格子都可以看成是一个细胞。每个细胞都具有一个初始状态:1 即为活细胞(live),或 0 即为死细胞(dead)。每个细胞与其八个相邻位置(水平,垂直,对角线)的细胞都遵循以下四条生存定律:

如果活细胞周围八个位置的活细胞数少于两个,则该位置活细胞死亡;
如果活细胞周围八个位置有两个或三个活细胞,则该位置活细胞仍然存活;
如果活细胞周围八个位置有超过三个活细胞,则该位置活细胞死亡;
如果死细胞周围正好有三个活细胞,则该位置死细胞复活;
根据当前状态,写一个函数来计算面板上所有细胞的下一个(一次更新后的)状态。下一个状态是通过将上述规则同时应用于当前状态下的每个细胞所形成的,其中细胞的出生和死亡是同时发生的。

示例:

输入:
[
[0,1,0],
[0,0,1],
[1,1,1],
[0,0,0]
]
输出:
[
[0,0,0],
[1,0,1],
[0,1,1],
[0,1,0]
]

class Solution {
    private int[] dx = {-1,-1,-1,0,1,1,1,0};
    private int[] dy = {-1,0,1,1,1,0,-1,-1};

    public void gameOfLife(int[][] board) {
       int m = board.length;
        if(m==0)
        return;
       int n = board[0].length;
        if(n==0)
        return;
        int[][] ans = new int[m][n];
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                game(i,j,board[i][j],board,ans,m,n);
            }
        }
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                board[i][j]=ans[i][j];
            }
        }
    }

    private void game(int i,int j,int state,int[][] board,int[][] ans,int m,int n){
        int cnt1=0;
        int cnt2=0;
        for(int k=0;k<8;k++){
            int x=i+dx[k];
            int y=j+dy[k];
            if(x>=0&&x<m&&y>=0&&y<n){
                if(board[x][y]==0){
                    cnt1++;//死亡细胞数
                }else{
                    cnt2++;//活细胞数
                }
               
            }
        }
        if(state==1&&cnt2==2){
        ans[i][j]=1;
        }
        if(state==1&&cnt2==3){
        ans[i][j]=1;
        }
        if(state==0&&cnt2==3)
        ans[i][j]=1;

    }

}

6

77. 组合

给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。

示例:

输入: n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]

class Solution {
    List<List<Integer>> lists = new ArrayList<>();
    public List<List<Integer>> combine(int n, int k) {
        if(n<k)
        return lists;
        List<Integer> list = new ArrayList<>();
        dfs(n,k,1,list);
        return lists;
    }

    public void dfs(int n,int k,int start,List<Integer> list){
        if(list.size()==k){
            lists.add(new ArrayList<>(list));
            return;
        }
        for(int i=start;i<=n;i++){
            list.add(i);
            dfs(n,k,i+1,list);
            list.remove(list.size()-1);
        }
    }
}

7

39. 组合总和

给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的数字可以无限制重复被选取。

说明:

所有数字(包括 target)都是正整数。
解集不能包含重复的组合。
示例 1:

输入: candidates = [2,3,6,7], target = 7,
所求解集为:
[
[7],
[2,2,3]
]
示例 2:

输入: candidates = [2,3,5], target = 8,
所求解集为:
[
[2,2,2,2],
[2,3,3],
[3,5]
]

class Solution {

    private List<List<Integer>> lists = new ArrayList<>();
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        if(candidates==null||candidates.length==0||target<0)
        return lists;
        List<Integer> list = new ArrayList<>();
        dfs(candidates,0,target,list);
        return lists;
    }

    public void dfs(int[] candidates,int start,int target,List<Integer> list){
        if(target<0){
        return;
        }

        if(target==0){
            lists.add(new ArrayList<>(list));
        }
        for(int i=start;i<candidates.length;i++){
            list.add(candidates[i]);
            dfs(candidates,i,target-candidates[i],list);
            list.remove(list.size()-1);
        }
    }
}

题目给出的算法结构为

class Solution {
public List<List> combinationSum(int[] candidates, int target) {

}

}
首先题目要求返回的类型为 List<List>,那么我们就新建一个 List<List> 作为全局变量,最后将其返回。

class Solution {
List<List> lists = new ArrayList<>();
public List<List> combinationSum(int[] candidates, int target) {

    return lists;
}

}
再看看返回的结构,List<List>。因此我们需要写一个包含 List 的辅助函数,加上一些判断条件,此时结构变成了

class Solution {
List<List> lists = new ArrayList<>();
public List<List> combinationSum(int[] candidates, int target) {
if (candidates == null || candidates.length == 0 || target < 0) {
return lists;
}

    List<Integer> list = new ArrayList<>();
    process(candidates, target, list);
    return lists;
}

private void process(int[] candidates, int target, List<Integer> list) {


}

}
重点就是如何进行递归。递归的第一步,当然是写递归的终止条件啦,没有终止条件的递归会进入死循环。那么有 哪些终止条件呢?由于条件中说了都是正整数。因此,如果 target<0,当然是要终止了,如果 target==0,说明此时找到了一组数的和为 target,将其加进去。此时代码结构变成了这样。

class Solution {
List<List> lists = new ArrayList<>();
public List<List> combinationSum(int[] candidates, int target) {
if (candidates == null || candidates.length == 0 || target < 0) {
return lists;
}

    List<Integer> list = new ArrayList<>();
    process(candidates, target, list);
    return lists;
}

private void process(int[] candidates, int target, List<Integer> list) {
    if (target < 0) {
        return;
    }
    if (target == 0) {
        lists.add(new ArrayList<>(list));
    }
   

}

}
我们是要求组成 target 的组合。因此需要一个循环来进行遍历。每遍历一次,将此数加入 list,然后进行下一轮递归。代码结构如下。

class Solution {
List<List> lists = new ArrayList<>();
public List<List> combinationSum(int[] candidates, int target) {
if (candidates == null || candidates.length == 0 || target < 0) {
return lists;
}

    List<Integer> list = new ArrayList<>();
    process(candidates, target, list);
    return lists;
}

private void process(int[] candidates, int target, List<Integer> list) {
    if (target < 0) {
        return;
    }
    if (target == 0) {
        lists.add(new ArrayList<>(list));
    } else {
        for (int i = 0; i < candidates.length; i++) {
            list.add(candidates[i]);
            //因为每个数字都可以使用无数次,所以递归还可以从当前元素开始
            process( candidates, target - candidates[i], list);
  
        }
    }

}

}
似乎初具规模,测试一把结果如下

结果差距有点大,为何会出现如此大的反差。而且发现一个规律,后面的一个组合会包含前面一个组合的所有的数字,而且这些数加起来和 target 也不相等啊。原因出在哪呢?java 中除了几个基本类型,其他的类型可以算作引用传递。这就是导致 list 数字一直变多的原因。因此,在每次递归完成,我们要进行一次回溯。把最新加的那个数删除。此时代码结构变成这样。

class Solution {
List<List> lists = new ArrayList<>();
public List<List> combinationSum(int[] candidates, int target) {
if (candidates == null || candidates.length == 0 || target < 0) {
return lists;
}

    List<Integer> list = new ArrayList<>();
    process(candidates, target, list);
    return lists;
}

private void process(int[] candidates, int target, List<Integer> list) {
    if (target < 0) {
        return;
    }
    if (target == 0) {
        lists.add(new ArrayList<>(list));
    } else {
        for (int i = 0; i < candidates.length; i++) {
            list.add(candidates[i]);
            //因为每个数字都可以使用无数次,所以递归还可以从当前元素开始
            process( candidates, target - candidates[i], list);
            list.remove(list.size() - 1);
        }
    }

}

}
再测一下,结果如下:

还是不对。这次加起来都等于 7 了,和上次结果相比算是一个很大的进步了。分析下测试结果。不难能看出,本次结果的主要问题包含了重复的组合。为什么会有重复的组合呢?因为每次递归我们都是从 0 开始,所有数字都遍历一遍。所以会出现重复的组合。改进一下,只需加一个 start 变量即可。 talk is cheap, show me the code。

代码如下:

List<List> lists = new ArrayList<>();

public List<List<Integer>> combinationSum(int[] candidates, int target) {
    if (candidates == null || candidates.length == 0 || target < 0) {
        return lists;
    }

    List<Integer> list = new ArrayList<>();
    process(0, candidates, target, list);
    return lists;
}

private void process(int start, int[] candidates, int target, List<Integer> list) {
    //递归的终止条件
    if (target < 0) {
        return;
    }
    if (target == 0) {
        lists.add(new ArrayList<>(list));
    } else {
        for (int i = start; i < candidates.length; i++) {
            list.add(candidates[i]);
            //因为每个数字都可以使用无数次,所以递归还可以从当前元素开始
            process(i, candidates, target - candidates[i], list);
            list.remove(list.size() - 1);
        }
    }

}

最后再测一下。

代码通过,但是效率并不高。本题有效果更好的动态规划的解法。本文主要展示递归回溯,就不做具体介绍了。

7

40. 组合总和 II

给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的每个数字在每个组合中只能使用一次。

说明:

所有数字(包括目标数)都是正整数。
解集不能包含重复的组合。
示例 1:

输入: candidates = [10,1,2,7,6,1,5], target = 8,
所求解集为:
[
[1, 7],
[1, 2, 5],
[2, 6],
[1, 1, 6]
]
示例 2:

输入: candidates = [2,5,2,1,2], target = 5,
所求解集为:
[
[1,2,2],
[5]
]

思路:该题与之前的区别在于同一次搜索使用过的数字是不能再使用的,所以这里每次向下搜索下表都加1,另外当和相等时,判断是否重复,如果重复则跳过,否则加入集合

class Solution {
    List<List<Integer>> lists = new ArrayList<>();
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        if(candidates==null||candidates.length==0||target<0){
            return lists;
        }
        Arrays.sort(candidates);
        List<Integer> list = new ArrayList<>();
        dfs(candidates,0,target,list);
        return lists;
    }

    public void dfs(int[] candidates,int start,int target,List<Integer> list){
        if(target<0)
        return;
        if(target==0){
            if(!lists.contains(list))
            lists.add(new ArrayList<>(list));
            return;
        }
        for(int i=start;i<candidates.length;i++){
            list.add(candidates[i]);
            dfs(candidates,i+1,target-candidates[i],list);
            list.remove(list.size()-1);
        }
    }
}

8

216. 组合总和 III

找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。

说明:

所有数字都是正整数。
解集不能包含重复的组合。
示例 1:

输入: k = 3, n = 7
输出: [[1,2,4]]
示例 2:

输入: k = 3, n = 9
输出: [[1,2,6], [1,3,5], [2,3,4]]

思路:该题与第六题相似,重点在于使用的数组是自己给出,在1-9之间,并且同一次搜索中不能重复使用,所以我们在每次搜索中对索引下标做加1操作,结束之后进行删除回退。

class Solution {
    List<List<Integer>> lists = new ArrayList<>();
    public List<List<Integer>> combinationSum3(int k, int n) {
        if(k*9<n)
        return lists;
        dfs(k,1,n,new ArrayList<>());
        return lists;
    }

    public void dfs(int k,int start,int n,List<Integer> list){
        if(n<0)
        return ;
        if(n==0){
            if(list.size()==k)
            lists.add(new ArrayList<>(list));
            return;
        }
        for(int i=start;i<=9;i++){
            list.add(i);
            dfs(k,i+1,n-i,list);
            list.remove(list.size()-1);
        }
    }
}

9

377. 组合总和 Ⅳ

给定一个由正整数组成且不存在重复数字的数组,找出和为给定目标正整数的组合的个数。

示例:

nums = [1, 2, 3]
target = 4

所有可能的组合为:
(1, 1, 1, 1)
(1, 1, 2)
(1, 2, 1)
(1, 3)
(2, 1, 1)
(2, 2)
(3, 1)

请注意,顺序不同的序列被视作不同的组合。

class Solution {
        //递归超时,这里使用dp思想
       public int combinationSum4(int[] nums, int target) {
        int[] dp = new int[target + 1];
        // 这个值被其它状态参考,设置为 1 是合理的
        dp[0] = 1;

        for (int i = 1; i <= target; i++) {
            for (int num : nums) {
                if (num <= i) {
                    dp[i] += dp[i - num];
                }
            }
        }
        return dp[target];
    }
}




因此输出为 7。

10

发布了4 篇原创文章 · 获赞 0 · 访问量 16

猜你喜欢

转载自blog.csdn.net/smile_study1/article/details/105600456