Leetcode 40组合总数(回溯)Ⅱ&41缺失的第一个正数&42接雨水

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

组合总数(回溯)



题目描述:


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

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


说明:

所有数字(包括目标数)都是正整数。

解集不能包含重复的组合。


示例 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]
]


分析,这题和组合总数Ⅰ有所不同,上一题是任意一个数可以出现任意多次并且形式上没有重复的。


但是这题有重复的但是每个数只能使用一次。如果按照上一题的分析思路就是就是回溯的过程需要进行下一个,并且需要一个哈希对结果去重。


但是我们都知道哈希虽然效率还行但是频繁的判断很影响算法的效率,有什么好的方法去解决?我们在回溯算法上进行合理剪枝。用一个策略去重


首先对于这个序列,我们先进行排序,排完序之后我们进行一下分析:


怕重复?无非是怕以下的这种情况嘛:


20201017174843441.png


而我们在实际处理的时候,同一个元素如果用多次,只能从第一个元素顺序使用而不能跳跃使用。


20201017183120433.png


在具体的处理方式我,刚开始我用了比较笨的方法:每次找到这个元素找到它相同的区间。然后遍历这个区间排列所有个数(从左向右),同时判断满足条件(数值总和不超出的情况下),递归下一个节点到下一个数值的编号上。当然不使用该元素在最后特殊进行一次dfs即可。


List<Integer> list = new ArrayList<Integer>();// list用来回溯过程中储存元素
List<List<Integer>> val = new ArrayList<List<Integer>>();// 结果
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
  Arrays.sort(candidates);
  dfs(0, candidates, target);
  return val;
}
/*
 * index 表示当前编号 count 表示当前数值的和
 */
private void dfs(int index, int[] candidates, int target) {
  // System.out.println(list.toString());
  if (target == 0) {
    val.add((new ArrayList<Integer>(list)));
    return;
  } else if (index == candidates.length) {
    return;
  } else {
    int r = index;
    while (r < candidates.length && candidates[index] == candidates[r]) {
      r++;
    }
    int i;
    for (i = index; i < r; i++) {
      target -= candidates[i];
      list.add(candidates[i]);
      if (target>=0) {
        dfs(r, candidates, target);
      } else {//没有执行dfs但是数值需要恢复 因为i还是变了
        target += candidates[index];
        list.remove(list.size() - 1);
        break;
      }
    }
    for (; i > index; i--) {
      target += candidates[index];
      list.remove(list.size() - 1);
    }
    dfs(r,  candidates, target);// 不使用该元素的
  }
}


20201017183946852.png


算法 4ms不太行,因为提前计算出右r的位置的时候进行一部分重复量挺大的运算。参考了别人的代码,才发现具体处理上可以这么处理:


遍历时候正常遍历,只不过进行遍历的时候判断如果该元素不是这次首元素的话和前面元素相等那么直接跳过。否则正常进行dfs。这样就可以过滤掉所有重复情况:


20201017185153943.png


最终ac的代码为:


List<Integer> list = new ArrayList<Integer>();// list用来回溯过程中储存元素
List<List<Integer>> val = new ArrayList<List<Integer>>();// 结果
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
  Arrays.sort(candidates);
  dfs(0, candidates, target);
  return val;
}
/*
 * index 表示当前编号 count 表示当前数值的和
 */
private void dfs(int index, int[] candidates, int target) {
  if (target == 0) {
    val.add((new ArrayList<Integer>(list)));
    return;
  }
  for(int i=index;i<candidates.length;i++)
  {
    if(target-candidates[i]<0)break;
    if(i!=index&&candidates[i]==candidates[i-1])continue;
    list.add(candidates[i]);
    dfs(i+1, candidates, target-candidates[i]);
    list.remove(list.size()-1);
  }
}

2020101718583382.png


缺失的第一个正数



20201017185913146.png


对于这题,如果不要求其他的,给一个数组空间的话,可以使用数组作为索引,直接干0ms:


public int firstMissingPositive(int[] nums) {
  int time[]=new int[nums.length+1];
  for(int i=0;i<nums.length;i++)
  {
    if(nums[i]>0&&nums[i]<nums.length)
    {
      time[i]++;
    }
  }
  for(int i=1;i<time.length;i++)
  {
    if(time[i]==0)
      return i;
  }
  return nums.length;
   }


但问题是人家要求的是常数级别空间。还是没想出来忍不住看了下题解恍然大悟:


数组妙用。分析问题,求未出现的最小正数,也就是最理想的情况是1.否则就找个未出现的最小的。这题核心的想法是复用数组,我们只需标记这个位置的数字有没有出现过! 所以可以用负数表示。


按照以下三点分析,首先我们知道这个数组中可能有负数或者大于数组长度数值的数存在,我们第一次遍历要将这些数置为1,并且看看1是否存在。如果不存在直接返回1即可。


2020101719323092.png

第二次遍历,将对应编号位置数改为负数(注意初始正负)。注意取值。


20201017194018912.png


第三次遍历找到第个为正数的那个位置,返回编号加1.如果为正数说明这个数未出现。

具体ac代码为:


 public int firstMissingPositive(int[] nums) {
    boolean jud =false;
    //第一轮循环,将无用的数字置为1 如果整个数列中没有1 那么将返回1
    for(int i=0;i<nums.length;i++)
    {
      if(nums[i]<=1||nums[i]>nums.length)
      {
        if(nums[i]==1)jud=true;
        nums[i]=1;
      }
    }
    if(!jud)return 1;
    //第二轮循环,所有数字现在目前的状态是1 - nums.length区间内
    for(int i=0;i<nums.length;i++)
    {
      int index=Math.abs(nums[i])-1;//该位置对应的索引
      if(nums[index]>0) {nums[index]=-nums[index];}
    }
    for(int i=0;i<nums.length;i++)
    {
      if(nums[i]>0)return i+1;
    }
    return nums.length+1;
    }


20201017194526630.png


接雨水



20201018144227454.png


题目分析


遇到这种题总有那么种似曾分析的感觉,我来谈谈如何分析这道题。


我的个人思路来说,我觉得这道题的核心就是看看能不能找到中间最高的元素。


20201018163008793.png


找到这个最高点后


  • 左右进行相等操作,从左来看,需要一个llteam,让lteam往右试探.
  • 试探途中如果height[lteam]<height[l],继续前进,后面一定会有更大的。
  • 试探途中如果height[lteam]>height[l],计算途中雨点和,然后l赋值为lteam,继续试探。


而同样右侧是进行一个从右往左的操作。


不过再具体实现上,本人初次只考虑从左往右然后遇到没有比它更大的找个次大的计算雨水。然后重新进行。具体代码为:


public int trap(int[] height) {
    int l=0,r=l+1;
  int count=0;
  int max=0,maxindex=0;
  while (l<height.length&&r<height.length) {
    if(height[r]>=height[l])//找到更大的靠山
    {
      for(int i=l;i<r;i++)
      {
        count+=height[l]-height[i];
      }
      l=r;
      r++;
      max=0;maxindex=0;
    }
    else {//不大于
      if(height[r]>=max)
      {
        max=height[r];
        maxindex=r;
      }
      r++;
      if(r==height.length)
      {
        for(int i=l+1;i<maxindex;i++)
        {
          count+=max-height[i];
        }
        l=maxindex;r=l+1;
        max=0;maxindex=0;
      }
    }
  }
  return count;
   }


20201018164618572.png


效果一般般因为遇到逆序形的会O(n2)算法。最好情况O(n)。

而换一种O(n)的写法之后,找到最大后右侧往左一次即可:


public int trap(int[] height) {
int l=0,r=l+1;
int count=0;
while (l<height.length&&r<height.length) {
  if(height[r]>=height[l])//找到更大的靠山
  {
    for(int i=l;i<r;i++)
    {
      count+=height[l]-height[i];
    }
    l=r;r=l+1;
  }
  else {//不大于
    r++;
    if(r==height.length)//说明当前的left已经是天底下最大的值了
    {
      int max=0;
      for(int i=r-1;i>l;i--)
      {
        if(height[i]>max)
        {
          max=height[i];
        }
        else
        {
          count+=max-height[i];
        }
      }
      l=r;
    }
  }
}
return count;


效果还行:


20201018165038973.png


结语



原创不易,bigsai请你帮两件事帮忙一下:


  1. star支持一下, 您的肯定是我在平台创作的源源动力。
  2. 微信搜索「bigsai」,关注我的公众号,不仅免费送你电子书,我还会第一时间在公众号分享知识技术。加我还可拉你进力扣打卡群一起打卡LeetCode。


目录
相关文章
|
2月前
Leetcode第41题(缺失的第一个正数)
这篇文章介绍了解决LeetCode第41题“缺失的第一个正数”的两种方法:使用哈希表和调整数组元素位置,以实现时间复杂度为O(n)且只使用常数级别额外空间的解决方案。
42 0
Leetcode第41题(缺失的第一个正数)
|
7月前
leetcode377组合总数4刷题打卡
leetcode377组合总数4刷题打卡
43 0
|
算法
代码随想录Day21 回溯 LeetCodeT216 组合总和III LeetCode T17电话号码的字母总和
代码随想录Day21 回溯 LeetCodeT216 组合总和III LeetCode T17电话号码的字母总和
53 0
|
6月前
|
机器学习/深度学习 存储 算法
Python5种算法回溯+剪枝、字典序、递归交换、计数回溯、迭代法 实现全排列ll【力扣题47】
Python5种算法回溯+剪枝、字典序、递归交换、计数回溯、迭代法 实现全排列ll【力扣题47】
|
6月前
|
算法 数据挖掘 开发者
LeetCode题目55:跳跃游戏【python5种算法贪心/回溯/动态规划/优化贪心/索引哈希映射 详解】
LeetCode题目55:跳跃游戏【python5种算法贪心/回溯/动态规划/优化贪心/索引哈希映射 详解】
|
6月前
|
算法
【经典LeetCode算法题目专栏分类】【第3期】回溯问题系列:单词搜索、N皇后问题、判断有效数独、解数独
【经典LeetCode算法题目专栏分类】【第3期】回溯问题系列:单词搜索、N皇后问题、判断有效数独、解数独
|
6月前
|
机器学习/深度学习 存储 算法
LeetCode题目 90:五种算法 回溯\迭代\位掩码\字典树\动态规划实现 子集ll
LeetCode题目 90:五种算法 回溯\迭代\位掩码\字典树\动态规划实现 子集ll
|
6月前
|
存储 算法 数据挖掘
LeetCode题目41:缺失的第一个正数
LeetCode题目41:缺失的第一个正数
|
7月前
leetcode-41:缺失的第一个正数
leetcode-41:缺失的第一个正数
36 0
|
7月前
|
算法
六六力扣刷题回溯之全排列
六六力扣刷题回溯之全排列
40 0