LeetCode刷题(贪心算法)

1.分发饼干

得排序一下。

class Solution {
    public int findContentChildren(int[] g, int[] s) {
    int count1=0;//用来记录满足了几个人
    int count2=s.length;//记录一下,我们的饼干还剩多少
    Arrays.sort(g);//这里得排序一下
    Arrays.sort(s);
    for(int i=0;i<s.length;i++){
        for(int j=0;j<g.length;j++)
            if(s[i]>=g[j]&&g[j]!=0&&count2>0){
                count1++;
                g[j]=0;
                count2--;
                break;//这里得break一下,因为里面这个饼干找到了主人
            }
        
    }
      return count1;
    }
}

2.摆动序列

这个序列里,差值一正一负的。 

class Solution {
    //只需要让当前差值与上一个差值,符号不一样即可。
    public int wiggleMaxLength(int[] nums) {
        if (nums.length <= 1) {
            return nums.length;
        }
        //当前差值
        int curDiff = 0;
        //上一个差值
        int preDiff = 0;
        int count = 1;
        for (int i = 1; i < nums.length; i++) {
            //得到当前差值
            curDiff = nums[i] - nums[i - 1];
           //所有的差值,可以看成一个新的数组。如果i这个位置满足,那就 count++;如果不满足那就下一个i+1

            if ((curDiff > 0 && preDiff <= 0) || (curDiff < 0 && preDiff >= 0)) {
                count++;
                preDiff = curDiff;
            }
        }
        return count;
    }
}

3.最大子序和(一组数连续相邻和的最大值)

累加的结果为负数,直接让count等于0。重新开始累加。

从-2出发的,为什么不从1那里重新出发,然后加呢?

其实从1那里,count已经<0了,已经从1那里从头开始了。然后1和-3的和小于0。

因为4之前的结果累加是负数。

class Solution {
    //思路:遇到负数,一定要重头开始count
    //我的卡壳:我认为,卡壳的时候得回到区间起始的地方的下一位。得两层for
    public int maxSubArray(int[] nums) {
        if (nums.length == 1){
            return nums[0];
        }
        int sum = Integer.MIN_VALUE;//让sum取最小值先。
        int count = 0;//记录增长的数量,当增长降到0以下的时候。就是最大和
        
        for (int i = 0; i < nums.length; i++){
            count += nums[i];
            sum = Math.max(sum, count); // 取区间累计的最大值(相当于不断确定最大子序终止位置)
            if (count <= 0){
                count = 0; 

//确保了重新开始的地方,如果不从这重新开始,前面的负数累积和,可能会拖累整体的结果。

            }
        }
       return sum;
    }
}

4.买卖股票的最佳时机

 思路:只需要把相邻两天的利润做成一张表。只收集正利润即可。

class Solution {
    //看看差值最大呗。但是这个差值可以累计。
    //先挑一天,最大差值的那一天,算上这个差值。然后再从这一天继续出发。找它之后最大的差值。再相加
    public int maxProfit(int[] prices) {
        int[] profit=new int [prices.length-1];
        for(int i=0;i<prices.length-1;i++){
            profit[i]=prices[i+1]-prices[i];
 
        }
        int sum=0;

        for(int i=0;i<profit.length;i++){
            if(profit[i]>0){
                sum+=profit[i];
            }

        }
        return sum;

    }
}

 5.跳跃问题

思路:在能跳的范围内,看看跳的最远能不能超过最后一位的下标。

class Solution {
 
    public boolean canJump(int[] nums) {
        if(nums.length==1){
            return true;
        }
        int len=0;//能跳多远。
        //在能跳范围内,更新最大的能跳范围
        for(int i=0;i<=len;i++){
              len=Math.max(len,i+nums[i]);//求出来,活动范围。
              if(len>=nums.length-1){
                  return true;
              }
        }
       return false;
    }

注意这里,for的上限,是能跳的范围。这个能跳的范围会逐步更新。

6.跳跃问题二

如果第一步的覆盖范围,走到了尽头,还没有到终点,那就开始走第二步,判断第二步的覆盖范围,是否包括终点。

思路:走当前的层。当走到当前层最后一个地方,还是没能到终点,就到跳到下一层。

走当前层的时候,每走一步都会更新下一层最远到的地方,走到当前层的最后一个地方,下一层要走的,也就更新完毕了。

class Solution {

    public int jump(int[] nums) {
        if (nums == null || nums.length == 0 || nums.length == 1) {
            return 0;
        }
        int count=0;//当前步数。
        int curDistance=0;//站在起点能走的最远的。第一层
        int  maxDistance=0;//走完第一层,最大的就会更新。然后走第二层。count++。如果走第二层时,maxDisrance刷新了,>=终点下标。

        for(int i=0;i<nums.length;i++){
            maxDistance=Math.max(maxDistance,i+nums[i]);//max,是这层走完,下层要走的。
            if(maxDistance>=nums.length -1){
                count++;
                return count;
            }
            if(i==curDistance){
                curDistance=maxDistance;//进入下个区域
                count++;
            }
        }
       return count;
     

}
}

 7.k次取反后最大数组和

情况分好,一步步做就行。

class Solution {
    //排序一下,找到正负分割的地方,如果有0,记录0的位置。
    //情况1,负数的个数》=k,从前到后全部反转。
    //情况2,负数的个数《k,取反的次数会多余,2.1:如果剩余次数是2的倍数,不用管。2.2如果或剩余次数对2取余为1,那么,如果有0,全部作用于0;如果没有0,比较正负分割的那两个数的下标的值。看看哪个小,让哪个变成负数。
    //情况3,如果没有负数,就看看k对二取余的情况。
    public int largestSumAfterKNegations(int[] nums, int k) {

    int fu=0;//负数的个数
    Boolean flag=false;//看看是否有0这个位。
    Arrays.sort(nums);

        for(int i=0;i<nums.length;i++){
             if(nums[i]==0){
                 flag=true;
             }
            if(nums[i]<0){
                fu++;
            }
        }

        if(fu>=k){
            for(int i=0;i<k;i++){
                nums[i]=-nums[i];
            }

        }else if(fu>0&&fu<k){
            int work=fu;
            for(int i=0;i<work;i++){
                 nums[i]=-nums[i];
            }
            int a=(k-work)%2;
            if(a==0){
                return sum(nums);
            }else{
                if(flag){
                    return sum(nums);
                }else{
                    if(fu<nums.length){//存在正数
                         if(nums[fu-1]>=nums[fu]){
                         nums[fu]=-nums[fu];
                         return sum(nums);

                     }else{
                         nums[fu-1]=-nums[fu-1];
                         return sum(nums);
                     }

                    }else{//不存在整数
                       nums[fu-1]=-nums[fu-1];
                       return sum(nums);

                    }
                    
                }
            }
        }else if(fu==0){
            int a=k%2;
            if(a==0){
                return sum(nums);
            }else{//对k取余为1时。
                if(flag){
                    return sum(nums);
                }else{
                    nums[0]=-nums[0];
                    return sum(nums);
                }
            }


        }
        return sum(nums);


    }
    int sum(int[] nums){
        int s=0;
        for(int num:nums){
            s+=num;
        }
        return s;
    }


}

8.加油站(不好理解)

C++的暴力解法,挨个走一遍。

class Solution {
public:
    int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
        for (int i = 0; i < cost.size(); i++) {
            int rest = gas[i] - cost[i]; // 记录剩余油量
            int index = (i + 1) % cost.size();
            while (rest > 0 && index != i) { // 模拟以i为起点行驶一圈
                rest += gas[index] - cost[index];
                index = (index + 1) % cost.size();
            }
            // 如果以i为起点跑一圈,剩余油量>=0,返回该起始位置
            if (rest >= 0 && index == i) return i;
        }
        return -1;
    }
};

一个for循环的优化。

// 思路,cursum来记录从开始点出发,剩余的油量。
//当前油量如果小于0了,那么就让下一站作为起始点curSum = 0;。如果走剩下几站,油箱里累加多出来的,能填补前面几站产生的负数,那就说明,能走一圈。
class Solution {
    public int canCompleteCircuit(int[] gas, int[] cost) {
        int  curSum=0;
        int totalSum=0;
        int index=0;//用来记录出发的起点
        for(int i=0;i<gas.length;i++){
            curSum+=gas[i]-cost[i];
            totalSum+=gas[i]-cost[i];
            if(curSum<0){
                curSum=0;
                index=(i+1)%gas.length;//i的下一站就是出发点。
            }
        }
        if(totalSum<0) return -1;//即便这样,最后走完,也没能填补之前的负数。
       return index;
       
    }
}

9.柠檬水找零

class Solution {
    public boolean lemonadeChange(int[] bills) {
        int  count5=0;//记录5美元的数量
        int count10=0;//记录10美元的数量
        for(int i=0;i<bills.length;i++){
            if(bills[i]==5){
                count5++;

            }
            if(bills[i]==10){
                count5--;
                count10++;
                if(count5<0){
                    return false;
                }
                
            }
            if(bills[i]==20){
                if(count5>=1&&count10>=1){//先找给别人10块的,没10再给5。
                    count5--;
                    count10--;
                   continue;
                }else{
                    if(count5>=3){//优先找10块。
                      count5-=3;
                    }else{
                        return false;
                    }
                }
               
            }
        

    }
    return true;
}
}

10.根据身高重建队列

 遇到两个维度权衡的时候,一定要先确定一个维度,再确定另一个维度。

queue[j] = [hj, kj] 。people[i] = [hi, ki] 表示第 i 个人的身高为 hi ,前面 正好 有 ki 个身高大于或等于 hi 的人。

先确定还是k先确定h呢,也就是究竟先按h排序呢,还先按照k排序呢?

思路:先把身高都排好序,然后按身高H的优先级。重新往队列里插入。按照身高排序之后,优先按身高高的people的k来插入,后序插入节点也不会影响前面已经插入的节点,最终按照k的规则完成了队列。

 那为什么让k当下标插入呢?因为已经让最高的先进去了。后面来的矮的位次就由k决定了。

class Solution {
    public int[][] reconstructQueue(int[][] people) {
   
        // 身高从大到小排(身高相同k小的站前面)
        Arrays.sort(people, (a, b) -> {
            if (a[0] == b[0]) return a[1] - b[1];
            return b[0] - a[0];
        });

        LinkedList<int[]> que = new LinkedList<>();
//后面按k插入,k就是插入的下标。
        for (int[] p : people) {
            que.add(p[1],p);//LinkedList这个add方法(本质是链表的插入)。插入,就伴随着后面元素都往后移动一位。
        }

        return que.toArray(new int[people.length][]);
    }
}

11.分发糖果

 刚开始的错误思路:给所有的孩子都一个,挨个遍历相邻,谁大,就再给谁一个。这个思路错误的,如果是1,2,3,这3个孩子最终得到的糖果是,1+2+3=6,而不是5+2。

正确思路:每次比较两个,确定一边之后,再确定另一边,例如比较每一个孩子的左边,然后再比较右边,如果两边一起考虑一定会顾此失彼

遇到两个维度权衡的时候,一定要先确定一个维度,再确定另一个维度。

先从左到右,考虑右边的。确定左孩子大于右孩子的情况一定要从后向前遍历!

如果ratings[i] > ratings[i - 1] 那么[i]的糖 一定要比[i - 1]的糖多一个。

class Solution {
    public int candy(int[] ratings) {
        int len=ratings.length;
         int[] a=new int[len];//整个数组记录孩子们的奖励情况
         
        a[0]=1;
        //先处理右边,从左到右,每俩只看右边。
    for(int i=0;i<len-1;i++){
        if(ratings[i+1]>ratings[i]){
              a[i+1]=a[i]+1;
        }else{
            a[i+1]=1;
        }
    }
    //再处理左边,从右到左,每俩只看左边。
    for(int i=len-1;i>=1;i--)
    if(ratings[i-1]>ratings[i]){
        if(a[i-1]<a[i]+1)
    //反例子,123452,比右边比完的时候,比左面。如果这俩人,左面那个人的糖已经大于右边的那个+1,那就没有必要再赋值。如果小于右边那个+1,就让它等于右边那个+1。
        a[i-1]=a[i]+1;
    }
    int sum=0;
    for(int num:a){
        sum+=num;
    }
    return sum;
    }
}

12.用最少的箭射爆气球。

首先得对这这个气球数组,排序一下。按照第一个元素排序。

13,24,这俩就是可以被连续射爆的,区间有交集。每次判断相邻两个,如果能有交集,就能一起被射爆。

如果没有交集,只能多一发箭,继续判断剩下的相邻。

注意:13,24,56,如果在5.6位,没能一起射爆,那第一箭也影响不到后面的气球,因为已经排序了。

class Solution {
    //如果有交集,17,26,34算一只箭。每次判断为同一只箭后,将气球的第二元素,改成第一箭的第二元素。
    public int findMinArrowShots(int[][] points) {
        //o1,o2长度为2的数组,代表气球的区间。
        Arrays.sort(points,(o1,o2)->Integer.compare(o1[0],o2[0]));
        //从第二位开始判断,第一位肯定要射出一发箭。
          int count=1;
          for(int i=1;i<points.length;i++){
              //判断后面那个是否于前面那个有交集,如果后面的第一个元素,大于前面的第二个元素,这俩没有交集。
              if(points[i][0]>points[i-1][1]){
                  count++;

              }else{
//如果有交集,那就让后面这个气球,的第二个元素,记录一下,这一箭的边界。边界就是前一个的第二个元素。
                  points[i][1]=Math.min(points[i][1],points[i-1][1]);
              }
          }
           return count;
    }
    
}

13.移除重叠区间

 首先得让区间都按规则排序一下,然后挨个比较。如果重叠,则count+1.

如果没有重叠更新右边界。

下面算法比较右边界的,所以让右边界升序即可。。

class Solution {
    public int eraseOverlapIntervals(int[][] intervals) {
    
        Arrays.sort(intervals,(a,b)->{
           return a[1]-b[1];//让右边界排,按升序,
           
        });
        int count=0;
        //如果是比较右边界的,让右边界升序。
        //找个变量记录一下右边界。如果下一个区间的,左边界,大于记录的右边界,那么它就重叠了。就除掉它。
         int edge=Integer.MIN_VALUE;
        for(int i=0;i<intervals.length;i++){
            //没有重叠,更新右边界
           if(edge<=intervals[i][0]){
               edge=intervals[i][1];
           }else{
               count++;
           }

        }

        return count;

    }
}

14.划分字母区间

class Solution {
    //思路:比如我们在找a的结尾的时候,不可避免的让别的字母也进来了。比如b,最终要到的右边界在变大。
    //右边界在找a的结尾时,不断变大,当我们亲自走到这个这个边界的时候,就说明,这个时候该add了。
    public List<Integer> partitionLabels(String s) {
        List<Integer> list=new ArrayList<>();

        Map<Character,Integer> map=new HashMap<>();
        for(int i=0;i<s.length();i++){
            char c=s.charAt(i);
            map.put(c,i);
        }//记录每一个字符最后出现的位置。
     int max=0;//用max来记录最大范围。
     int len=0;
        for(int i=0;i<s.length();i++){
            len++;
             char c=s.charAt(i);
           int a=map.get(c);
           if(a>max) max=a;//不断的更新最大的终点,当来到最大的地方的时候,就add进去。

            if(i==max){
               list.add(len);
               len=0;
                continue;
            }

    }
     return list;
   
}

}

15.合并区间

 合并所有重叠的区间,返回一个不重叠的。先将区间排序一下,如果这俩有交集,新生成一个大区间。

class Solution {
    public int[][] merge(int[][] intervals) {
        LinkedList<int[]> list=new LinkedList<>();
        Arrays.sort(intervals,(a,b)->{
            if(a[0]==b[0]) return a[1]-b[1];
            return a[0]-b[0];
        });
        //这里严格排序一下。

    list.addLast(intervals[0]);
    //考虑重复重叠的问题。不断与栈顶的数组做比较
      for(int i=1;i<intervals.length;i++){
          int[] work=list.peekLast();

          if(intervals[i][0]<=work[1]){
              list.removeLast();
              int[] a=new int[2];
              a[0]=work[0];
              a[1]=Math.max(work[1],intervals[i][1]);
              list.addLast(a);

          }else{
              list.addLast(intervals[i]);
              
          }

      }
      int[][] result=new int[list.size()][2];
      for(int i=0;i<list.size();i++){
           result[i]=list.get(i);
          //把结果从list里放进数组。
      }
      return result;

        

    }
}

16.单调递增的数字

 有暴力解法。从后往前,挨个判断是否是最近的递增。

class Solution {
    public int monotoneIncreasingDigits(int n) {
        int k=n;
        while(true){
            if(work(k)){
                break;
            }
         k--;
        }
        return k;
        

    }
    //判断k是否单调递增。
    Boolean work(int k){
        List<Integer> list=new ArrayList<>();
  while(k!=0){
      list.add(k%10);
      k=k/10;
      }
      for(int i=0;i<list.size()-1;i++){
          if(list.get(i)<list.get(i+1)){
              return false;
          }
      }
       return true;

    }
    
}

非暴力解法:在数组上进行修改,找到最左面那个开始大于后面的下标,让它减1。然后这个下标之后的都为9。

class Solution {
   //找到start位,让它减1,然后后面的全都变成9,就行。
   
    public int monotoneIncreasingDigits(int n) {
        String s = String.valueOf(n);
        char[] chars = s.toCharArray();
        int start9 = s.length();
        for (int i = s.length() - 2; i >= 0; i--) {
            if (chars[i] > chars[i + 1]) {//找到最左面开始不满足的位,让那个位减1。

                chars[i]--;
                /
                start9 = i+1;//9开始的地方。
            }
            //最终让最左面的那个地方停在start。让start位减了1。
        }
        for (int i = start9; i < s.length(); i++) {
            chars[i] = '9';
        }
        return Integer.parseInt(String.valueOf(chars));
       

    }
    
}

17.监控整个二叉树

摄像头放在叶子节点的父节点位置,才能充分利用摄像头的覆盖面积。

此时,大体思路就是从低到上,先给叶子节点父节点放个摄像头,然后隔两个节点放一个摄像头,直至到二叉树头结点。

此时这道题目还有两个难点:

  1. 二叉树的遍历:用后序,可以从底到上。
  2. 如何隔两个节点放一个摄像头。

第二个难点:结点有3个情况。

情况1:该结点已经被摄像头覆盖,有覆盖

情况2:这个结点已经有摄像头。

情况3:这个结点没有被摄像头覆盖,没覆盖

那如果访问到空结点,它是什么状态?

空结点,不能是没覆盖,不然它上面一层的叶子结点,就得安摄像头了。所以访问到空结点的时候,返回它是被覆盖的。

// 空节点,该节点有覆盖
if (cur == NULL) return 2;

我们从下往上整。

有4种情况。

1:左孩子有覆盖,右孩子有覆盖,那么此时中间节点应该就是无覆盖的状态了(从下网上呗)。

// 左右节点都有覆盖
if (left == 2 && right == 2) return 0;

2.左右子至少有一个没有被覆盖。这个时候,这个中间结点就必须得安摄像头了。

if (left == 0 || right == 0) {
    result++;
    return 1;
}

 3.左右孩子至少有一个有摄像头。

那这个左右孩子的父亲就得改成是覆盖的状态了。

4.整个过程处理完了之后,最后对根结点,判断一下。是否被覆盖。

如果没有被覆盖,就给他加一个摄像头。

这种情况是,两个子都是被覆盖的,而不是有一个摄像头。

int minCameraCover(TreeNode* root) {
    result = 0;
    if (traversal(root) == 0) { // root 无覆盖
        result++;
    }
    return result;
}

class Solution {
    int res=0;
    public int minCameraCover(TreeNode root) {
      if(travel(root)==0){
          res++;
      }
      return res;
    }
//三种状态,0表示无覆盖,1表示有摄像头,2表示被覆盖。只要子里没被覆盖,就要安摄像头。
    int travel(TreeNode root){
        if(root==null){
            return 2;//避免叶子结点被安摄像头,让叶子结点的儿子,返回2。

        }
        int left=travel(root.left);
        int right=travel(root.right);

        if(left==2&&right==2){//如果俩都被覆盖了,说明这个结点肯定还没被覆盖到。
            return 0;
        }else if(left==0||right==0){//如果有一个没覆盖的,就要装一波摄像头。
            res++;
            return 1;
        }else {//除了上面两种情况,剩下的都是至少有一个摄像头的。记住上面俩种情况即可
         return 2;
        }


    }
}

18. 盛最多水的容器

 找两根线,看这两根线能构成的最大的容器量。

 

1.用双指针来做,i开始指向最左端,j指向最右端。

2.然后移动 i 或者 j

3.左移或者右移动,容器底部长度都会减1,我们尽量保留比较高的边,移动边小的那一端,

因为保留较高那条边更容易出最大值。

class Solution {
    //找最后一个大于它的位置,和第一个它小于的位置。
    public int maxArea(int[] height) {
      int i=0;
      int j=height.length-1;
      int maxSpacity=0;
//左移或者右移动,容器底部长度都会减1,我们尽量保留比较高的边,移动边小的那一端,更容易出最大值。
      while(j>i)
      {   
          int num1=Math.min(height[i],height[j])*(j-i);
           maxSpacity=Math.max(num1,maxSpacity);
          int num2=height[i];
          int num3=height[j];
          if(num2>num3)
          {
              j--;
          }else{
              i++;
          }    
      }
      return maxSpacity;
}
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据引用\[1\]和引用\[2\]的内容,推荐的LeetCode刷题顺序是按照题目类型刷题,优先选择树、链表、二分查找、DFS、BFS、动态规划等常见类型的题目。可以先做2~4道简单题,然后再做中等难度的题目。在选择题目时,可以优先选择题目序号小、点赞多、提交成功率高的题目,这样可以从简单入手,节省时间。同时,LeetCode每道题目都有“模拟面试”功能,可以给自己设定时间限制,如果做不出来可以看答案,然后记住思路后再自己尝试一遍。每种类型的题目做完10+道后,可以总结规律。 根据引用\[3\]的内容,题目可以按照不同的分类进行刷题,比如数组与贪心算法、子数组与贪心算法、子序列与贪心算法、数字与贪心、单调栈法、双指针法等。可以根据自己的兴趣和需求选择相应的题目进行刷题。 综上所述,LeetCode刷题顺序可以按照题目类型或者题目分类进行选择。 #### 引用[.reference_title] - *1* [LeetCode 刷题顺序,按标签分类,科学刷题!](https://blog.csdn.net/fengyuyeguirenenen/article/details/125099023)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [leetcode 刷题指南 & 刷题顺序](https://blog.csdn.net/qijingpei/article/details/125561071)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [leetcode-刷题顺序推荐](https://blog.csdn.net/weixin_38087674/article/details/114107841)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值