代码随想录算法训练营周末一

LeetCode

  • 42. 接雨水(热题100, 双指针)
  • 2109. 向字符串添加空格(每日打卡)
  • 121. 买卖股票的最佳时机(热题100, 贪心)
  • 55. 跳跃游戏(热题100, 贪心)
  • 45. 跳跃游戏 II(热题100, 贪心)
  • 763. 划分字母区间(热题100, 贪心)
  • 总结
  • 往期打卡


42. 接雨水(热题100, 双指针)

跳转: 42. 接雨水

问题:

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

思路:

比较容易想到的是用快慢指针(其实和两层for循环遍历一样)一个坑一个坑的找,只要大于等于当前的柱子就跳转,但最坏的情况时间为 O ( n 2 ) O(n^2) O(n2);

可以用左右指针,以低的一边为界(因为左右都在最外侧,所以更低的情况可以视作坑内,故不存在更低边界),让边界低的向边界高的靠近,可以一遍遍历完成计算

复杂度:

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)

代码(左右指针):

class Solution {
    public int trap(int[] height) {
        int l = 0;
        int r = height.length - 1;
        int lMax = 0;
        int rMax = 0;
        int res = 0;
        while (l != r) {
            if (height[l] > lMax) {
                lMax = height[l];
            }
            if (height[r] > rMax) {
                rMax = height[r];
            }
            if (height[l] < height[r]) {
                res += lMax - height[l];
                l++;
            } else {
                res += rMax - height[r];
                r--;
            }
        }
        return res;
    }
}

复杂度:

  • 时间复杂度: O ( n 2 ) O(n^2) O(n2)
  • 空间复杂度: O ( 1 ) O(1) O(1)

代码(快慢指针):

class Solution {
    public int trap(int[] height) {
        int slow = 0;
        int fast = 1;
        int max = 0;
        int maxIndex = 0;
        int res = 0;
        while(slow<height.length-1){
            int H = height[slow];
            while(fast<height.length&&height[fast]<H){
                if(max<height[fast]){
                    max = height[fast];
                    maxIndex = fast;
                }
                fast++;
            }
            if(fast<height.length&&height[fast] >= H){
                for(int i = slow+1;i<fast;i++){
                    res+=H - height[i];
                }
                slow = fast;
            }
            else if(maxIndex>0){
                H = max>H?H:max;
                for(int i = slow+1;i<maxIndex;i++){
                    res+=H - height[i];
                }
                slow = maxIndex;
            }
            else{
                slow++;
            }
            fast = slow+1;
            maxIndex = 0;
            max = 0;
        }
        return res;
    }
}

2109. 向字符串添加空格(每日打卡)

跳转: 2109. 向字符串添加空格

问题:

给你一个下标从 0 开始的字符串 s ,以及一个下标从 0 开始的整数数组 spaces

数组 spaces 描述原字符串中需要添加空格的下标。每个空格都应该插入到给定索引处的字符值 之前

  • 例如,s = "EnjoyYourCoffee"spaces = [5, 9] ,那么我们需要在 'Y''C' 之前添加空格,这两个字符分别位于下标 5 和下标 9 。因此,最终得到 “Enjoy Your Coffee” 。

请你添加空格,并返回修改后的字符串。

思路:

StringBuilder 比 StringBuffer 少了同步机制,更快
初始化内存避免扩容开销
避免使用insert这种O(n)方法,选择使用append
批量append代替单字符,减少循环的开销
直接用append方法添加子串,避免substring临时对象的开销

复杂度:

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n)

代码:

class Solution {
    public String addSpaces(String s, int[] spaces) {
        StringBuilder ans = new StringBuilder(s.length() + spaces.length);
        int last = 0;
        for (int i = 0; i < spaces.length; i++) {
                ans.append(s, last, spaces[i]);
                last = spaces[i];
                ans.append(" ");
        }
        ans.append(s,last,s.length());
        return ans.toString();
    }
}

121. 买卖股票的最佳时机(热题100, 贪心)

跳转: 121. 买卖股票的最佳时机

问题:

给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。

你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。

返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0

思路:

每次用当前历史最低价卖出,根据价格更新最大利润

复杂度:

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)

代码:

class Solution {
    public int maxProfit(int[] prices) {
        int min = prices[0];
        int max = 0;
        for(int i = 1;i<prices.length;i++){
            if(min>prices[i]){
                min = prices[i];
            }
            else if(max<prices[i]-min){
                max = prices[i] -min;
            }
        }
        return max;
    }
}

55. 跳跃游戏(热题100, 贪心)

跳转: 55. 跳跃游戏

问题:

给你一个非负整数数组 nums ,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。

判断你是否能够到达最后一个下标,如果可以,返回 true ;否则,返回 false

思路:

首先想到的是用哈希,走一步看看当前点能不能到达,然后更新一遍可到达点
如果遇到不可到达的地方就说明不能到最后

可以直接记录最远可到达点
如果遇到最大无法到达就说明不能走到最后

复杂度:

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)

代码(最远点贪心):

class Solution {
    public boolean canJump(int[] nums) {
        int max = 0;
        int n = nums.length-1;
        for(int i = 0;i<=n;i++){
            if(max<i) return false;
            int tmp = i+nums[i];
            max = max>tmp?max:tmp;
            if(max>=n) return true;
        }
        return false; // 没用
    }
}

复杂度:

  • 时间复杂度: O ( n 2 ) O(n^2) O(n2)
  • 空间复杂度: O ( 1 ) O(1) O(1)

代码(哈希):

class Solution {
    public boolean canJump(int[] nums) {
        int n = nums.length;
        int[] hash = new int[n];
        hash[0] =1;
        for(int i=0;i<n;i++){
            if(hash[i]==1){
                int far = i+nums[i];
                for(int j = i+1;j<n&&j<=far;j++){
                    hash[j] = 1;
                }
            }
        }
        // System.out.println(Arrays.toString(hash));
        return hash[n-1]==1;
    }
}

45. 跳跃游戏 II(热题100, 贪心)

跳转: 45. 跳跃游戏 II

问题:

给定一个长度为 n0 索引整数数组 nums。初始位置为 nums[0]

每个元素 nums[i] 表示从索引 i 向后跳转的最大长度。换句话说,如果你在 nums[i] 处,你可以跳转到任意 nums[i + j] 处:

  • 0 <= j <= nums[i]
  • i + j < n

返回到达 nums[n - 1] 的最小跳跃次数。生成的测试用例可以到达 nums[n - 1]

思路:

贪心*2,每步走最远,记最远可到达点

复杂度:

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)

代码:

class Solution {
    public int jump(int[] nums) {
        if(nums.length==1) return 0;
        int n = nums.length;
        int maxStep = 0;
        int nextMax = 0;
        int steps = 0;
        for (int i = 0; i < n; i++) {
            if(maxStep<i){
                maxStep=nextMax;
                steps++;
                nextMax = i;
            }
            nextMax = Math.max(nextMax,i+nums[i]);
            if(nextMax>=n-1) return steps+1;
        }
        return -1;
    }
}

763. 划分字母区间(热题100, 贪心)

跳转: 763. 划分字母区间

问题:

给你一个字符串 s 。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。例如,字符串 "ababcc" 能够被分为 ["abab", "cc"],但类似 ["aba", "bcc"]["ab", "ab", "cc"] 的划分是非法的。

注意,划分结果需要满足:将所有划分结果按顺序连接,得到的字符串仍然是 s

返回一个表示每个字符串片段的长度的列表。

思路:

一开始想的是记录所有字母的起点和终点的索引,遍历起点和终点,一段一段找
如果更新终点就重找(因为可能会有些起点在新的区间里)
不过实现起来相对复杂

可以记录终点,然后遍历字符串
到达当前段终点时记录位置,更新起点

复杂度:

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)

代码(遍历字符串):

class Solution {
    public List<Integer> partitionLabels(String s) {
        int n = s.length();
        int[] last = new int[26];
        int length = s.length();
        for (int i = n - 1; i >= 0; i--) {
            int tmp = s.charAt(i) - 'a';
            if (last[tmp] == 0) {
                last[tmp] = i;
            }
        }
        List<Integer> res = new ArrayList<>();
        int start = 0;
        int end = 0;
        for (int i = 0; i < n; i++) {
            end = Math.max(end, last[s.charAt(i) - 'a']);
            if (i == end) {
                res.add(end - start + 1);
                start = end + 1;
            }
        }
        return res;
    }
}

复杂度:

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)

代码(遍历起点和终点):

class Solution {
    public List<Integer> partitionLabels(String s) {
        int n = s.length();
        int[] start = new int[26];
        int[] end = new int[26];
        char[] arr = s.toCharArray();
        for (int i = 0; i < n; i++) {
            int tmp = arr[i] - 'a';
            if (start[tmp] == 0) {
                start[tmp] = i+1;
            }
        }
        for (int i = n - 1; i >= 0; i--) {
            int tmp = arr[i] - 'a';
            if (end[tmp] == 0) {
                end[tmp] = i+1;
            }
        }
        // System.out.println(Arrays.toString(start));
        // System.out.println(Arrays.toString(end));
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < n;) {
            int tmp = arr[i] - 'a';
            int st = start[tmp];
            int e = end[tmp];
            int[] hash = new int[26];
            int pre;
            do {
                pre = e;
                for (int j = 0; j < 26; j++) {
                    if (start[j] > st && start[j] < e) {
                        hash[j] = 1;
                    }
                }
                for (int j = 0; j < 26; j++) {
                    if (hash[j] == 1) {
                        if (end[j] > e)
                            e = end[j];
                    }
                }
            } while (e != pre);
            // System.out.println(st);
            // System.out.println(e);
            list.add(e - st + 1);
            i = e;
        }

        return list;
    }
}

总结

今天主要是练习了几道贪心
用贪心求解感觉在于找到那个使当前最优的关键状态变量,像当前段终点,最远可到达点,历史最低价
都是基于当前状态逐步决策,在每步决策中求局部最优解
这种思维后续还需要继续练习

往期打卡

代码随想录算法训练营第一天
代码随想录算法训练营第二天
代码随想录算法训练营第三天
代码随想录算法训练营第四天

你可能感兴趣的:(代码随想录打卡,算法)