leetcode解题数组篇

一、二分查找

1. 704 “二分查找”

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/binary-search
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

    int search(vector<int>& nums, int target) {
        int i = 0, j = nums.size() - 1;
        while (i <= j)
        {
            int temp = (i + j) / 2;
            if (nums[temp] < target)
                i = temp + 1;
            else if (nums[temp] > target) 
                j = temp - 1;
            else
                return temp;
        }
        return -1;
    }

最基础的左闭右闭区间查找

纯纯的二分查找题目不难,只需要理清楚所查找的数组的类型,保证在不断更新数组的时候,数组的类型不发生变化即可,即保持二分查找中的不变量

2. 35 “插入位置搜索”

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

请必须使用时间复杂度为 O(log n) 的算法。

    int searchInsert(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size()-1;
        while(left<=right){
            int middle = (left+right)/2;
            if(nums[middle] < target){
                left = middle+1;
            }else if(nums[middle] > target){
                right = middle - 1;
            }else if(nums[middle] == target){
                return middle;
            }
        }
        return left;
    }

这道题相当于是二分查找的变种,唯一的区别是在目标数组中不存在元素时的操作。那么可以分两种情况讨论,第一种是当目标数组中存在target元素时,那么直接返回元素下标,与单纯的二分查找无异,第二种情况下,left和right变量必然是left>right并且并未返回一个元素下标,并且,经过对二分查找的分析,当left>right时,即循环结束时,left = right + 1的,所以,无论最后一步是left发生变化还是right发生变化,target元素必定是满足这样的不等式:nums[right]<target<nums[left],综合即可以得知target应该是要插入到最后left所在的元素的下标,即left。

3. 69 “x的平方根”

给你一个非负整数 x ,计算并返回 x 的 算术平方根 。

由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。

注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/sqrtx
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

    int mySqrt(int x) {

        int left = 0;
        int right = x;
        while(left<=right){
            long middle = (left + right) / 2;
            if(middle * middle < x){
                left = middle + 1;
            }else if(middle * middle > x){
                right = middle - 1;
            }else if(middle * middle == x){
                return middle;
            }
        }
        return right;
    }

这题主要考虑的因素是暴力算法会超时,所以我们采用二分算法,基本就是一个变形,之前我陷入的地方是如果找到二分查找的上界,但是由于二分查找时间复杂度为logn,所以直接将上界简单滴定为x也可以解决问题。也就是说,二分查找的这个上界可以不是那么准确,因为不符合条件的区域也会被pass掉。

4. 34 “在排序数组中查找元素的第一个位置和最后一个位置”

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

这题主要是二分法的一个变形,常规的二分法是用于查找排序数组中等于target的元素的下标,而此题的意思是要查找第一个大于等于target的元素的下标和第一个大于target的元素的下标,即将等于替换成其他的,并利用二分法进行查找

区间两侧都是闭合的情况下,二分查找的终止是left>right,也就是left = right + 1

区间为左开右闭的情况下,二分查找的终止是left = right 这也是众多语言内置的二分查找API的标准写法

    vector<int> searchRange(vector<int>& nums, int target) {

        int left = search_left(nums,target);
        int right = search_right(nums,target);
        
        if(left >= nums.size() || nums[left] != target){
            return vector<int>{-1,-1};
        }else{
            return vector<int>{left,right};
        }
    }

    int search_left(vector<int>& nums, int target){
        int left = 0;
        int right = nums.size();
        while(left < right){
            int middle = (left + right) / 2;
            if(nums[middle] >= target){
                right = middle;
            }else {
                left = middle + 1;
            }
        }
        return right;
    }

    int search_right(vector<int>& nums, int target){
        int left = 0;
        int right = nums.size();
        while(left < right){
            int middle = (left + right) / 2;
            if(nums[middle] <= target){
                left = middle + 1;
            }else {
                right = middle;
            }
        }
        return left - 1;
    }

// 左闭右开区间返回值略有不同是因为left 和 right的收敛操作不同,推演一遍两种搜寻过程即容易弄明白
// 搜寻最左边以right为准 最右边以left为准



// 第二种:两边闭区间的写法
    vector<int> searchRange(vector<int>& nums, int target) {

        int left = search_left(nums,target);
        int right = search_right(nums,target);
        
        if(left >= nums.size() || nums[left] != target){
            return vector<int>{-1,-1};
        }else{
            return vector<int>{left,right};
        }
    }

    int search_left(vector<int>& nums, int target){
        int left = 0;
        int right = nums.size() - 1;
        while(left <= right){
            int middle = (left + right) / 2;
            if(nums[middle] >= target){
                right = middle - 1;
            }else {
                left = middle + 1;
            }
        }
        return left; // right + 1
    }

    int search_right(vector<int>& nums, int target){
        int left = 0;
        int right = nums.size() - 1;
        while(left <= right){
            int middle = (left + right) / 2;
            if(nums[middle] <= target){
                left = middle + 1;
            }else {
                right = middle - 1;
            }
        }
        return right; // left -1 
    }

经过一些资料的查阅和阅读,此类问题可以归结于有重复元素的二分查找,分别查找重复元素的最左和最右,也可以看作是找到第一个大于等于target元素的。 可以没有该元素

5. 367 “有效的完全平方数”

给定一个 正整数 num ,编写一个函数,如果 num 是一个完全平方数,则返回 true ,否则返回 false 。

进阶:不要 使用任何内置的库函数,如 sqrt 。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/valid-perfect-square
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

    bool isPerfectSquare(int num) {
        long left = 1;
        long right = num;
        while(left <= right){
            long mid = left + (right - left) / 2;

            if(mid * mid < num){
                left = mid + 1;
            }else if(mid * mid > num){
                right = mid - 1;
            }else{
                return true;
            }
        }
        return false;

    }

这题和69题基本是一样的,使用二分法即可

6. 二分法及其变式总结

二分法最重要的是确定区间不变量,无论是区间不变量是哪一种类型,只要在更新区间的时候保持这个不变量,那么就都可以实现相应的二分查找。

  • 左闭右闭:left = 0 ; right = length - 1; left <= right ;left = middle + 1; right = middle - 1; 结束时left =right +1
  • 左闭右开:left = 0 ; right = length ; left < right ;left = middle + 1; right = middle; 结束时left =right

详细可参考:https://www.zhihu.com/question/36132386

二分法还有一点就是上下界可以任选,不一定要精确到某一位,因为不在范围内的数字很容易会被收敛掉。参考 69,367 。

二、移除元素 (双指针)

1. 27 “移除元素”

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。

元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/remove-element
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

    int removeElement(vector<int>& nums, int val) {
        int len = nums.size();
        int index = nums.size()-1;
        for(int i = nums.size() - 1 ; i >= 0 ; i--){
            printf("nums[i] = %d\n",nums[i]);
            if(nums[i]==val){
                printf("val = %d\n",val);
                nums[i] = nums[index];
                len--;
                index--;
            }
        }
        return len;
    }


    int removeElement(vector<int>& nums, int val) {
        int slowIndex = 0;
        for (int fastIndex = 0; fastIndex < nums.size(); fastIndex++) {
            if (val != nums[fastIndex]) {
                nums[slowIndex++] = nums[fastIndex];
            }
        }
        return slowIndex;
    }

这题普遍的思路是使用双指针的思想,包括力扣上的官方解答等等,这题我的思路是将所有等于val的元素全部移到数组的最后,根据本题目的要求,只检查前len个数组的元素是否正确,所以从元素最后开始往前遍历,维持两个指针,一个是遍历指针i,另一个指针维护的是数组中下一个和遍历指针所指示的元素进行交换的元素下标,在遍历移动的过程中,对len的值进行更新,该方法能保证将所有等于val的元素全部移动到数组后方。

官方做法也是双指针,不过稍有不同,官方做法是快慢指针,快指针用于遍历,慢指针用于覆盖原来的数组,大致思路是:快指针从0位置逐个遍历,如果元素不等于val,则在慢指针所示位置填上该元素,随后,快慢指针一起移动;如果元素等于val,则快指针移动,慢指针不动,即将所有不等于val的元素全部通过慢指针在原数组上进行写入操作。

2. 26.删除排序数组中的重复项

给你一个 升序排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。

由于在某些语言中不能改变数组的长度,所以必须将结果放在数组nums的第一部分。更规范地说,如果在删除重复项之后有 k 个元素,那么 nums 的前 k 个元素应该保存最终结果。

将最终结果插入 nums 的前 k 个位置后返回 k 。

不要使用额外的空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

    int removeDuplicates(vector<int>& nums) {
        if(nums.size() == 0)
            return 0;
        int i = 1 , j = 1;
        int temp = nums[0];
        for(i = 1 ; i < nums.size() ; i++){
            if(nums[i] == temp){
                continue;
            }else{
                nums[j] = nums[i];
                j++;
                temp = nums[i];
            }
        }
        return j;
    }

和之前的一样,原地修改的可以使用快慢指针来进行操作。

3. 283.移动零

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

请注意 ,必须在不复制数组的情况下原地对数组进行操作。

    void moveZeroes(vector<int>& nums) {
        int i = 0 , j = 0;
        // 非零元素全部移动到前面
        for(j = 0 ; j < nums.size(); j++){
            if(nums[j] != 0){
                nums[i] = nums[j];
                i++;
            }
        }
        for(; i < nums.size() ; i++){
            nums[i] = 0;
        }
    }
    
    // 官方做法
        void moveZeroes(vector<int>& nums) {
        int n = nums.size(), left = 0, right = 0;
        while (right < n) {
            if (nums[right]) {
                swap(nums[left], nums[right]);
                left++;
            }
            right++;
        }
    }

此题可以借助第27题的思想,将非零元素全部移动到前面,再通过循环将后面的元素全部赋值为0。官方做法是双指针遍历,直接将0元素移动到后面,一次循环即可,left指针和right指针中间的元素应全为0。

4. 844.比较含退格的字符串

给定 s 和 t 两个字符串,当它们分别被输入到空白的文本编辑器后,如果两者相等,返回 true 。# 代表退格字符。

注意:如果对空文本输入退格字符,文本继续为空。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/backspace-string-compare
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

    bool backspaceCompare(string s, string t) {

        if(get(s) == get(t))
            return true;
        return false;
    }

    string get(string str){
        stack <char> strStack;
        for(char s : str){
            if(s != '#'){
                strStack.push(s);
            }else if(!strStack.empty()){
                strStack.pop();
            }
        }

        string result = "";
        
        while(!strStack.empty()){
            result += strStack.top();
            strStack.pop();
        }
        return result;
    }


// 官方解答是直接使用字符串的操作 ,c++中字符串的操作还挺多。。
class Solution {
public:
    bool backspaceCompare(string S, string T) {
        return build(S) == build(T);
    }

    string build(string str) {
        string ret;
        for (char ch : str) {
            if (ch != '#') {
                ret.push_back(ch);
            } else if (!ret.empty()) {
                ret.pop_back();
            }
        }
        return ret;
    }
};

这题本意是可以使用双指针,但是显然使用栈会更加简单,主要思路也很清晰,求出两个参数的最终结果进行比较即可,主要涉及的是堆栈的操作。

5. 977.有序数组的平方

给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。

class Solution {
    public int[] sortedSquares(int[] nums) {
        int left = 0;
        int right = nums.length - 1;

        int[] res = new int[nums.length];
        int index = nums.length - 1;

        while (left != right) {
            if (nums[left] * nums[left] <= nums[right] * nums[right]) {
                res[index] = nums[right] * nums[right];
                right--;
            } else {
                res[index] = nums[left] * nums[left];
                left++;
            }
            index--;
        }

        res[index] = nums[left] * nums[left];
        return res;
    }
}

这题是去年11月份写的,思路是数组左右各一个指针,比较指针所指元素绝对值的大小,然后依次向中间移动指针,最终得到新的数组。

三、长度最小的子数组 (滑动窗口)

1. 209.长度最小的子数组

给定一个含有 n 个正整数的数组和一个正整数 target 。

找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, …, numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/minimum-size-subarray-sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

暴力解法:

可以通过OJ,主要思路是将子数组分类,所有的子数组可以分为第一个元素开头的数组、第二个元素开头的数组、…、第n个元素开头的数组,进行一个n次的外层for循环,再找出每个元素开头的最小子数组,其实内层也是一个贪心算法,一旦大于等于target即停止while循环。最后再做个判断返回0的即可。

    int minSubArrayLen(int target, vector<int>& nums) {
        int res = nums.size();
        int total = 0;
        for(int i = 0 ; i < nums.size() ; i++ ){
            int j = i ;
            int sum = 0;
            total +=nums[i];
            while(j < nums.size() && sum <target){
                sum += nums[j];
                j++;
            }
            if(sum >= target)
                res = (j - i) < res ? (j - i) : res;
        }
        if(total >= target) 
            return res;
        return 0;
    }

滑动窗口解法:

    int minSubArrayLen(int target, vector<int>& nums) {
        // 滑动窗口
        int i = 0 , j = 0;
        int res = nums.size();
        bool flag = false;

        while(j < nums.size()){
            int sum = 0;
            for(int k = i; k <= j ; k++){
                sum += nums[k];
            }
            if(sum < target)
                j++;
            else{
                flag = true;
                res = (j - i) + 1 < res ? (j - i) + 1 : res;
                i++;
            }
        }
        return flag ? res : 0;
    }

// 用时最好的一种解法
    int minSubArrayLen(int target, vector<int>& nums) {
        // 滑动窗口
        int i = 0 , j = 0;
        int res = nums.size() + 1;
        int sum = 0;
        for (; j<nums.size() ; j++){
            sum +=nums[j];
            while(sum >= target){
                res = (j - i + 1) < res ? (j - i + 1) : res ;
                sum -= nums[i];
                i++;
            }

        }
        return res == nums.size() + 1 ? 0 : res;

    }

第二种主要是避免了多次累加求sum的操作,然后同样是对子数组类型进行了一个分类,以子数组结尾元素分成了n类,每当子数组内元素和大于target,记录下最小长度,并移动窗口左端,减少窗口元素,并简化了sum的求法;每当小于target,移动窗口右端,增加窗口元素,也就是换成了另一类子数组继续求。

2. 904.水果成篮

你正在探访一家农场,农场从左到右种植了一排果树。这些树用一个整数数组 fruits 表示,其中 fruits[i] 是第 i 棵树上的水果 种类 。

你想要尽可能多地收集水果。然而,农场的主人设定了一些严格的规矩,你必须按照要求采摘水果:

你只有 两个 篮子,并且每个篮子只能装 单一类型 的水果。每个篮子能够装的水果总量没有限制。
你可以选择任意一棵树开始采摘,你必须从 每棵 树(包括开始采摘的树)上 恰好摘一个水果 。采摘的水果应当符合篮子中的水果类型。每采摘一次,你将会向右移动到下一棵树,并继续采摘。
一旦你走到某棵树前,但水果不符合篮子的水果类型,那么就必须停止采摘。
给你一个整数数组 fruits ,返回你可以收集的水果的 最大 数目。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/fruit-into-baskets
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

class Solution {
    public int totalFruit(int[] tree) {
        if (tree == null || tree.length == 0) return 0;
        int n = tree.length;

        Map<Integer, Integer> map = new HashMap<>();
        int maxLen = 0, left = 0;
        for (int i = 0; i < n; i++) {
            map.put(tree[i], map.getOrDefault(tree[i], 0) + 1); 
            while (map.size() > 2) { 
                map.put(tree[left], map.get(tree[left]) - 1);
                if (map.get(tree[left]) == 0) map.remove(tree[left]); 
                left++;
            }
            maxLen = Math.max(maxLen, i - left + 1);
        }
        return maxLen;
    }
}

大致思路是,用HashMap来保证子数组中只有两种类型的数字,当出现第三种类型的时候,就要移动左指针,并更新HashMap的值,直到HashMap中只有两种类型的数字,然后每次移动右指针的时候更新maxLen的值,最终达到目的。自己写的那个有一些bug,主要是移除类型的时候有些问题没有考虑全面,删去了错误的那个类型,所以导致错误。

leetcode评论区还有一种方法,之后复习的时候可以试试

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
题目描述:给定一个非负整数数组nums一个整数m,你需要将这个数组分成m个非空的连续子数组。设计一个算法使得这m个子数组中的最大和最小。 解题思路: 这是一个典型的二分索题目,可以使用二分查找来解决。 1. 首先确定二分的左右边界。左边界为数组中最大的值,右边界为数组中所有元素之和。 2. 在二分索的过程中,计算出分割数组的组数count,需要使用当前的中间值来进行判断。若当前的中间值不够分割成m个子数组,则说明mid值偏小,将左边界更新为mid+1;否则,说明mid值偏大,将右边界更新为mid。 3. 当左边界小于等于右边界时,循环终止,此时的左边界即为所求的结果。 具体步骤: 1. 遍历数组,找到数组中的最大值,并计算数组的总和。 2. 利用二分查找索左右边界,从左边界到右边界中间的值为mid。 3. 判断当前的mid值是否满足题目要求,若满足则更新右边界为mid-1; 4. 否则,更新左边界为mid+1。 5. 当左边界大于右边界时,循环终止,返回左边界即为所求的结果。 代码实现: ```python class Solution: def splitArray(self, nums: List[int], m: int) -> int: left = max(nums) right = sum(nums) while left <= right: mid = (left + right) // 2 count = 1 total = 0 for num in nums: total += num if total > mid: total = num count += 1 if count > m: left = mid + 1 else: right = mid - 1 return left ``` 时间复杂度分析:二分索的时间复杂度为O(logN),其中N为数组的总和,而遍历数组的时间复杂度为O(N),因此总的时间复杂度为O(NlogN)。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值