力扣刷题3-数组字符串、双指针、系统设计

159 阅读11分钟
1 数组字符串
1.1 合并两个有序数组

题目描述:合并 nums2 到 nums1中,使合并后的数组同样按 非递减顺序排列 标签:数组,简单 时间复杂度O(m+n) 题解:遍历nums1,nums2,遍历两数组每次取较小者。

1.2 原地移除元素

题目描述:给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素。元素的顺序可能发生改变。然后返回 nums 中与 val 不同的元素的数量。 标签:数组,简单 题解:双指针,空间O(1),时间O(n)

public int removeElement(int[] nums, int val) {
        int l=0, r=0;
        int n = nums.length;
        while (l < n && r < n) {
            // 右指针元素待删除,右指针右移1位
            if (nums[r] == val) {
                r++;
            } else { // 右指针元素非删除,右指针元素拷贝到左指针位置,左、右指针同时右移1位
                nums[l] = nums[r];
                l++;
                r++;
            }
        }
        return l;
    }
1.3 删除有序数组中重复项

题目描述:给你一个 非严格递增排列 的数组 nums ,请你原地删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。
标签:数组,简单
题解:双指针法,fast表示遍历位置索引,slow表示下一个存入唯一元素的索引。注意fast和slow都是从1开始

public int removeDuplicates(int[] nums) {
        int n = nums.length;
        int slow = 1, fast = 1;
        while (fast < n) {
            if (nums[fast] != nums[fast - 1]) {
                nums[slow] = nums[fast];
                slow++;
            }
            fast++;
        }
        return slow;
    }
1.4 删除有序数组中重复项II

给你一个有序数组 nums ,请你原地删除重复出现的元素,使得出现次数超过两次的元素只出现两次 ,返回删除后数组的新长度。 不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。 标签:数组,中等

输入:nums = [1,1,1,2,2,3] 输出:5, nums = [1,1,2,2,3]
题解:双指针法,fast表示遍历位置索引,slow表示下一个存入元素的索引位置。fast和slow都是从2开始。
当nums[slow-2]=nums[fast]时,待检查元素nums[fast]不该保留(有序数组,如果相等,说明重复数等于3)

public int removeDuplicates(int[] nums) {
        int slow = 2, fast = 2;
        int n = nums.length;
        while (fast < n) {
            if (nums[fast] != nums[slow - 2]) {
                nums[slow++] = nums[fast];
            }
            ++fast;
        }
        return slow;
    }
1.5 多数元素

题目描述:给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。 你可以假设数组是非空的,并且给定的数组总是存在多数元素。

标签:数组,中等 尝试时间复杂度为 O(n)、空间复杂度为 O(1) 的算法 题解:排序,分析众数是最大值、最小值两种极端情况,取n/2位置元素,一定是所有情况下众数的值。

public int majorityElement(int[] nums) {
        Arrays.sort(nums);
        return nums[nums.length / 2];
    }

复杂度分析:时间O(nlogn), 空间O(logn)

1.6 轮转数组

题目描述:给定一个整数数组 nums,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。 标签:数组,简单 题解:i -> (i+k) % n,理解元素轮转的数学等价转换

public void rotate(int[] nums, int k) {
        int n = nums.length;
        int[] newArr = new int[n];
        for (int i = 0; i < n; ++i) {
            newArr[(i + k) % n] = nums[i];
        }
        System.arraycopy(newArr, 0, nums, 0, n);
    }
1.7 买卖股票

题目描述:给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。 返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0。

标签:数组,简单 题解:profit = p[j] - p[i],这里j>i 方法1 简单粗暴遍历,两重循环,时间复杂度O(n^2),肯定会超时 方法2 遍历第一遍得到每个点右侧最大价格(即第i天买入,最大售卖价格),遍历第二遍得到第i天利润最大,时间复杂度O(n)

// 第1次遍历,求值right_max_values
int[] right_max_values = new int[length];
int right_max_value = prices[length - 1];
for (int i = length-2; i >=0; i--) {
    if (prices[i] > right_max_value) {
        right_max_value = prices[i];
    }
    right_max_values[i] = right_max_value;
}

// 第2次遍历,取最大profit
int left=0;
while (left < (length - 1)) {
    int tmp = (right_max_values[left] - prices[left]);
    total = tmp > total ? tmp : total;
    left++;
}

1.8 买卖股票II

题目描述: 给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。 在每一天,你可以决定是否购买和/或出售股票。你在任何时候最多只能持有 一股股票。你也可以先购买,然后在同一天出售。 返回 你能获得的 最大利润。 标签:数组,中等

题解:profit = p[j] - p[i],这里j>i

示例: input=[7,1,5,3,6,4] output= 7 第2天买入,第3天卖出 profit = 5 - 1=4 第4天买入,第5天卖出 profit = 6 - 3=3 total = 4 + 3 = 7

题解:官方题解动态规划,然而更简单题解思路是,总结最大化利润规律:股价上升买入,下跌卖出,注意最后一次必须全部卖出

for (; i< (prices.length - 1); i++) {
    if (prices[i] < prices[i+1]) {  // 股价上升
        if (buyPrice == -1) {
            buyPrice = prices[i];
        }
    } else if (prices[i] > prices[i+1]) { // 股价下跌
        if (buyPrice >= 0) {
            profit += prices[i] - buyPrice;
            buyPrice = -1;
        }
    }
}
if (buyPrice >= 0) {
    profit += prices[i] - buyPrice;
}

1.9 H指数

题目描述:给你一个整数数组 citations ,其中 citations[i] 表示研究者的第 i 篇论文被引用的次数。计算并返回该研究者的 h 指数。 h指数定义:h 代表“高引用次数” ,一名科研人员的 h 指数 是指他(她)至少发表了 h 篇论文,并且 至少 有 h 篇论文被引用次数大于等于h。 标签:数组,中等 题解:引用次数排序 + 遍历 注意理解: citations[i] > h表示找到一篇至少引用h+1次论文,由于是从大到小遍历,所以至少存在h+1篇论文,被引用h+1次,即h++

Arrays.sort(citations);
for (int i = n - 1; i >= 0; i--) {
    if (citations[i] > h) {
        h++;
    }
}

2 系统设计
2.1 O(1)时间插入、删除和获取随机元素

题目描述: 实现RandomizedSet 类:

  • RandomizedSet() 初始化 RandomizedSet 对象
  • bool insert(int val) 当元素 val 不存在时,向集合中插入该项,并返回 true ;否则,返回 false
  • bool remove(int val) 当元素 val 存在时,从集合中移除该项,并返回 true ;否则,返回 false 。
  • int getRandom() 随机返回现有集合中的一项(测试用例保证调用此方法时集合中至少存在一个元素)。每个元素应该有 相同的概率 被返回。 实现上述4个函数,且满足每个函数平均时间复杂度O(1) 标签:中等,设计

题解:要求实现插入、删除、获取随机元素,数据结构选型:变长数组可以在O(1)时间获取随机元素,但不能O(1)插入、删除;哈希表可以在O(1)完成插入、删除,但不能根据下标获取元素。所以数据结构是 数组+哈希表。

注意理解:获取随机元素,数组下标都是连续,随机选取一个下标即可 重点:保持数组下标连续

插入操作: 1 在数组末尾添加val 2 map添加<val, index>

删除操作: 1 从map获取val的下标index 2 把变长数组最后一个元素last移动到下标index,map更新last的下标为index 3 变长数组删除last元素,哈希表删除val

获取随机元素: random.nextInt(nums.size());

public ArrayList<Integer> array;
    public HashMap<Integer, Integer> map;
    Random random;

    public RandomizedSet() {
        array = new ArrayList<>();
        map = new HashMap<>();
        random = new Random();
    }

    public boolean insert(int val) {
        if (map.containsKey(val)) {
            return false;
        }
        array.add(val);
        map.put(val, array.size()-1);
        return true;
    }

    public boolean remove(int val) {
        if (!map.containsKey(val)) {
            return false;
        }
        int index = map.get(val);
        int last_index = array.size() - 1;
        if (index < last_index) {
            int tmp = array.remove(last_index);
            array.set(index, tmp);
            map.put(tmp, index);
        } else if (index == last_index) {
            array.remove(index);
        }
        map.remove(val);
        return true;
    }

    public int getRandom() {
        int index = random.nextInt(array.size());
        return array.get(index);
    }
3 双指针
3.1 判断子序列

题目描述:给定字符串 s 和 t ,判断 s 是否为 t 的子序列。 例如,"ace"是"abcde"的一个子序列,而"aec"不是。

标签:简单,双指针

题解:双指针解法,初始化两个指针i、j,分别指向s、t,当s[i] == t[j]时,两个指针都右移,否则仅仅移动j,尝试用t的下一个元素匹配s[i],最后判断i是否达到末尾

public boolean isSubsequence(String s, String t) {
    int n = s.length(), m = t.length();
    int i = 0, j = 0;
    while (i < n && j < m) {
        if (s.charAt(i) == t.charAt(j)) {
            i++;
        }
        j++;
    }
    return i == n;
}

3.2 盛最多水的容器

题目描述:给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。 返回容器可以储存的最大水量。

标签:中等,双指针

题解:双指针算法,左右指针l、r分别指向两端,res = Min(h[l], h[r]) * (r-l)。h[l]和h[r]比较,应该移动较小指针,这样res值才不会变小,事实可以证明这是合理的。 关键代码如下:

int l = 0;
int r = height.length - 1;
int res = 0;
while (l < r) {
    int min = Math.min(height[l], height[r]);
    int size = min * (r - l);
    res = Math.max(size, res);
    if (height[l] > height[r] ) {
        r--;
    } else {
        l++;
    }
}
4 滑动窗口
4.1 长度最小的子数组 209

题目描述: 给定一个含有 n 个正整数的数组和一个正整数 target 。 找出该数组中满足其总和大于等于 target 的长度最小的子数组 [numsl, numsl+1, ..., numsr-1, numsr],并返回其长度。如果不存在符合条件的子数组,返回 0 。

标签:中等,滑动窗口 题解:怎么滑动窗口?双指针l、r,每次迭代加入nums[r]元素,如果sum>= target,滑动l,每次滑动都更新子数组长度。注意:求最小子数组:先滑动r一步,再循环滑动l 直至sum>=target 不满足

关键代码:

int minSubArrayLen(int target, vector<int> &nums) {
  int n = nums.size();
  int l = 0;
  int r = 0;
  int sum = 0; // 窗口内元素和
  int res = INT_MAX;
  while (r < n) {  // 滑动r一步
    sum += nums[r];
    while (sum >= target) { // 寻找所有满足条件的l,满足sum >= target
      sum -= nums[l];
      res = min(res, (r - l + 1));
      l++;
    }
    r++;
  }
  return (res < INT_MAX) ? res : 0;
}

##### 4.2 无重复字符的最长子串 3
题目描述:
给定一个字符串s,请找出不含重复字符的最长子串长度

标签:中等,滑动窗口
题解:左右指针l、r,l表示枚举子串的开始位置,r表示最长子串结束位置
注意:**求最长子串:先滑动l一步,再循环滑动r 直至!occ.count(s[r]) 不满足**  
滑动窗口解法:

int l = -1; // 左指针 int r = 0; // 右指针 set occ; //记录windown已出现字符 int res = 0; while (r < n) { // 滑动窗口left if (l >= 0) { occ.erase(s[l]); } l++; // 滑动窗口right,直至遇到重复字符s[r] while (r<n && !occ.count(s[r])) { occ.insert(s[r]); r++; } res = max(res, r-l); // 取子区间[l+1, r] } return res;


##### 4.3 最大连续1的个数 III 1004
题目描述: 给定一个字符串s,请找出不含重复字符的最长子串长度

标签:中等,滑动窗口 
题解:左右指针l、r。  
注意:**先滑动r一步,再循环滑动l,寻找满足条件最小l,即lsum < rsum-k**,这里比较绕,可以添加打印调试

int longestOnes(vector& nums, int k) { int n = nums.size(); int left = 0, lsum = 0, rsum = 0; int ans = 0; for (int right = 0; right < n; ++right) { //滑动一步r rsum += 1 - nums[right]; while (lsum < rsum - k) { //寻找最小l,满足lsum >= rsum - k,即 rsum-lsum <= k lsum += 1 - nums[left]; ++left; } ans = max(ans, right - left + 1); //取子区间[l, r] } return ans; }