LeetCode刷题第三周

在这里插入图片描述

我来啦,托更半天的我来啦。嘤嘤嘤,虽然并不是很想做算法题,但既然说了说每周一更新,那就保质保量的每周更一篇。放假半年,现在到了七月上旬,实在是学疲了,上周重装了系统,墨墨迹迹把电脑整舒服了很多,但是不管是看书还是看视频一会儿就犯困,果然有些理论学起来过于枯燥,这周得调整调整,下个月末就要开学啦~


数组专题

在这里插入图片描述

简单

1. 两数之和

题目链接:点击跳转至本题

题目大意:
有nums数组,和一个特定值target,要求:找到并返回两个下标,这两个下标要满足相加和为target。
注意:
①每种输入只会对应一个答案。
②数组中同一个元素不能使用两遍。

解题思路:
思路1:使用双层for循环
采用简单的双层for循环来完成这一匹配,需要注意一下返回的两个下标要以数组的形式呈现,使用一维数组 new int[ ]{i,j} 的形式。一层for循环的时间复杂度为 O ( n ) O(n) O(n),双重的时间复杂度为 O ( n 2 ) O(n^2) O(n2) 。这里在开始直接定义一个返回的数组,可以省略非法参数异常的抛出。

思路2:使用哈希表
虽然本篇是数组专题,不过这里遇到了,就使用HashMap来做一下。若仍不理解,可以等本专栏后面更新到HashMap时,再详细分析。
整体思路是:定义一个空的map,对nums数组仅做一次遍历,遍历的同时,将nums的键值对依次装载进map中。每使用put向前装载一个键值对,都使用map.containsKey向后查询一遍是否包含其中的一个元素值。若找到,就返回它的value(即下标)和另一个下标i。

// 1. 两数之和

//方法1:
class Solution {
    
    
    public int[] twoSum(int[] nums, int target) {
    
    
        int[] result = new int[2];
        for(int i = 0;i < nums.length;i++){
    
    
            for(int j = i + 1;j < nums.length;j++){
    
    
                if(nums[i] + nums[j] == target){
    
    
                    result[0] = i;
                    result[1] = j;
                }
            }
        }
        return result;
    }
}

//方法2: 
class Solution {
    
    
    public int[] twoSum(int[] nums, int target) {
    
    
        int[] result = new int[2];
        Map<Integer,Integer> map = new HashMap<>();
        for(int i = 0;i < nums.length;i++){
    
    
            int temp_num = target - nums[i];
            if(map.containsKey(temp_num)){
    
    
                result[0] = i;
                result[1] = map.get(temp_num);
            }
            map.put(nums[i], i);//key=数组值,value=数组下标
        }
        return result;
    }
}

在这里插入图片描述

在这里插入图片描述

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

题目链接:点击跳转至本题

题目大意:
给定一个排好序的数组nums,要求:原地删除重复出现的元素,使得每个元素只能出现一次,最后返回新数组的长度。
注意:不能使用额外的数组空间,且不需要考虑数组中超出新长度后面的元素。

解题思路:双指针法
数组是排好序的,因此出现重复时可以使用后面的来覆盖,间接完成"删除操作"。使用双指针法,定义两个变量(慢指针p和快指针q),一前一后遍历一遍数组。当发现nums[p] == nums[q]时,快指针q后移,当发现nums[p] != nums[q]时,慢指针p后移并将快指针q处的值赋给慢指针p处。可以想见被覆盖处一定是重复的数据。p和q代表的都是下标,新数组的长度为q+1,但去重后的数据长度只有p+1。

class Solution {
    
    
    public int removeDuplicates(int[] nums) {
    
    
        if(nums.length == 0){
    
    
            return 0;
        }
        //p作为慢指针
        int p = 0;
        //q作为快指针
        int q = 1;
        while(q < nums.length){
    
    
            if(nums[p] != nums[q]){
    
    
                p++;
                nums[p]=nums[q];
            }
            q++;
        }
        return p+1;
    }
}

27. 移除元素

题目链接:点击跳转至本题

题目大意
给定一个数组nums和一个值val,要求删除nums中元素值等于val的元素,返回新数组的长度。
注意:不能使用额外的数组空间,且不需要考虑数组中超出新长度后面的元素。

解题思路:双指针法
27题和26题很相似,关键点在于快指针q的初始值是0还是1。由于26题是和数组中前一个值比较,所以快指针q初始值为1;27题是和数组外的一个定值比较,所以快指针q初始值为0。

class Solution {
    
    
    public int removeElement(int[] nums, int val) {
    
    
        //p为慢指针
        int p = 0;
        //q为快指针
        int q = 0;
        while(q < nums.length){
    
    
            if(nums[q] != val){
    
    
                nums[p]=nums[q];
                p++;
            }
            q++;
        }
        return p;
    }
}

35. 搜索插入位置

题目链接:点击跳转至本题

题目大意
给定一个排好序的数组nums和一个目标值target,要求:在数组中找到该目标值,并返回其索引。如果没有此目标值,则返回目标值按顺序应该被插入的位置。

解题思路:二分查找
使用二分查找,分别设定左下标left和右下标right,再计算中间下标mid。然后每次根据 nums[mid] 和 target 之间的大小进行判断:

  • ①nums[mid] < target:直接返回下标。
  • ②nums[mid] < target : left 右移。
  • ③nums[mid] > target : right 左移。

本题中,如果查找结束没有找到与target相等的元素,则直接返回 下标left 即可,因为 下标left 就是符合题意该插入的位置。

class Solution {
    
    
    public int searchInsert(int[] nums, int target) {
    
    
        int left = 0, right = nums.length;
        while(left < right) {
    
     
            int mid = (left + right) / 2; 
            if(nums[mid] == target) {
    
    
                return mid;
            } else if(nums[mid] < target) {
    
    
                left = mid + 1; 
            } else {
    
    
                right = mid; 
            }
        }
        return left;
    }
}

在这里插入图片描述

中等

11. 盛最多水的容器

题目链接:点击跳转至本题

题目大意
a i a_i ai代表坐标中的一个点(i, a i a_i ai),点(i, a i a_i ai)和点(i,0)作为垂直线的两个端点,数组为a[1,8,6,2,5,4,8,3,7]。要求:找出两条垂直线,使它们与x轴共同构成的容器可以盛最多的水。
注意:容器不能倾斜,且n≥2。

解题思路:双指针法
盛最多的水,即求构成的矩形面积最大。设定左右指针 l 和 r 分别位于数组的左右两端,他们构成的面积为: m i n ( l , r ) ∗ ( r − l ) min(l , r) * (r - l) min(l,r)(rl),定义一个变量ans作为最大面积,在遍历中不断更新ans的值,遍历结束时,ans即为最大的面积。每一步当比较l和r的高度时,最小的那个高度向中间移动一位。

class Solution {
    
    
    public int maxArea(int[] height) {
    
    
        int l = 0;
        int r = height.length - 1;
        int ans = 0;
        while(l < r){
    
    
            //矩形面积=底x高(木桶原则)
            int area = Math.min(height[l],height[r]) * (r - l);
            ans = Math.max(ans,area);
            if(height[l] < height[r]){
    
    
                l++;
            }else{
    
    
                r--;
            }
        }
        return ans;
    }
}

15. 三数之和

题目链接:点击跳转至本题

题目大意
有nums数组,要求找出nums中的三个元素a、b、c,满足a+b+c=0。答案中不可以包含重复的组合。

解题思路:排序+双指针
三层for循环的时间复杂度是 O ( n 3 ) O(n^3) O(n3),可以对数组排序后先固定一个值,然后使用双指针法,将时间复杂度降低到 O ( n 2 ) O(n^2) O(n2)。本题不难,复杂在去重上,更详细的思路在代码注释中。

class Solution {
    
    
    public List<List<Integer>> threeSum(int[] nums) {
    
    // 总时间复杂度:O(n^2)
        //由于结果可能返回多个数组,故采用链表中套链表的结构
        List<List<Integer>> ans = new ArrayList<>();
        //此处简单的优化提速(可忽略),考虑了两种特殊情况
        if (nums == null || nums.length <= 2){
    
    
            return ans;
        } 
        //对nums数组进行快排O(nlogn)
        Arrays.sort(nums); 
 
        //for+while双层循环,O(n^2),这里的i作为相对固定指针。
        for (int i = 0; i < nums.length - 2; i++) {
    
     
            //此处简单的优化提速(可忽略),考虑了排好序后的第一个数应>0
            if (nums[i] > 0) break;

            // 去掉指针i的重复情况
            if (i > 0 && nums[i] == nums[i - 1]) {
    
    
                continue; 
            }
            //定义首尾指针
            int left = i + 1, right = nums.length - 1;
            while (left < right) {
    
    
                if (nums[left] + nums[right] +nums[i] == 0) {
    
    
                    ans.add(new ArrayList<>(Arrays.asList(nums[i], nums[left], nums[right])));
                    left++; right--;
                    //去掉指针left的重复情况
                    while (left < right && nums[left] == nums[left - 1]) left++;
                    //去掉指针right的重复情况
                    while (left < right && nums[right] == nums[right + 1]) right--;
                } else if (nums[left] + nums[right] + nums[i] < 0) {
    
    
                    left++;
                } else if (nums[left] + nums[right] + nums[i] > 0) {
    
      
                    right--;
                }
            }
        }
        return ans;
    }
}

16. 最接近的三数之和

题目链接:点击跳转至本题

题目大意
给定一个包括n个整数的数组nums和一个目标值target,要求找出nums中的三个整数使得它们的和与target最接近。假定每组输入只存在唯一的答案,返回这三个数的和。

解题思路:排序+双指针

此题与上一题 15. 三数之和 有很大相似之处,首先对数组进行排序,为使用双指针提供先决条件。固定指针i,定义 左指针left 和 右指针right ,假定三数之和为sum,
若sum == target,直接返回结果;
若sum < target,左指针left右移一位;
若sum > target,右指针right左移一位;
之后,对 target-sum 的绝对值不断更新,取较小值,不断赋给ans。

本题中对指针i、左右指针 left 和 right 都进行了类似15题那样的优化,不过,这些优化仅可以加快执行速度,不会改变时间复杂度 O ( n 2 ) O(n^2) O(n2)

class Solution {
    
    
    public int threeSumClosest(int[] nums, int target) {
    
    
        Arrays.sort(nums);
        int ans = nums[0] + nums[1] + nums[2];
        for(int i = 0;i < nums.length;i++){
    
    
            //对指针i去重
            if (i > 0 && nums[i] == nums[i - 1]) {
    
    
                continue;
            }

            int left = i+1,right = nums.length - 1;
            while(left < right){
    
    
                int sum = nums[i] + nums[left] + nums[right];
                //不断更新ans为最接近target的三数之和
                if(Math.abs(target - sum) < Math.abs(target - ans)){
    
    
                    ans = sum;
                }
                
                if(sum == target){
    
    
                        left++;
                        right--;
                        //去掉指针left的重复情况
                        while (left < right && nums[left] == nums[left - 1]){
    
    
                            left++;
                        }
                        //去掉指针right的重复情况
                        while (left < right && nums[right] == nums[right + 1]){
    
    
                            right--;
                        }
                    return target;
                }else if(sum < target){
    
    
                    left++;
                }else if(sum > target){
    
    
                    right--;
                } 
            }
        }
        return ans;
    }
}

18. 四数之和

题目链接:点击跳转至本题

题目大意
给定一个包含n给整数的数组nums和一个目标值target,要求找出nums中是否包含四个元素a、b、c、d,满足a+b+c+d=target,且所有满足条件的组合不能重复。

解题思路:
此题是15. 三数之和 的增强版,不同之处在于应多加一层for循环,使时间复杂度变为 O ( n 3 ) O(n^3) O(n3) ,整体思路为,定义两个相对固定的指针,代表a和b,再使用双指针left和right代表c和d。复杂点在于考虑四个指针的去重情况,与前面类似,不再赘述。

class Solution {
    
    
    public List<List<Integer>> fourSum(int[] nums, int target) {
    
    
        List<List<Integer>> ans = new ArrayList<>();
        //数组为null或元素<4时返回空集合
        if(nums == null || nums.length < 4){
    
    
            return ans;
        }
        Arrays.sort(nums);
        //第一层循环
        for(int i = 0;i < nums.length-3;i++){
    
    
            //对指针i进行去重
            if(i>0 && nums[i] == nums[i-1]){
    
    
                continue;
            }
            //第二层循环
            for(int j=i+1;j<nums.length-2;j++){
    
    
                //对指针j进行去重
                if(j>i+1 && nums[j] == nums[j-1]){
    
    
                    continue;
                }
                //第三次循环
                int left = j+1,right = nums.length - 1;
                while(left < right){
    
    
                    int sum = nums[i] + nums[j] + nums[left] + nums[right];
                    if(sum == target){
    
    
                        ans.add(Arrays.asList(nums[i],nums[j],nums[left],nums[right]));
                        left++;
                        right--;

                        //去掉指针left的重复情况
                        while (left < right && nums[left] == nums[left - 1]){
    
    
                            left++;
                        }
                        //去掉指针right的重复情况
                        while (left < right && nums[right] == nums[right + 1]){
    
    
                            right--;
                        }
                    }else if(sum < target){
    
    
                        left++;
                    }else if(sum > target){
    
    
                        right--;
                    }
                }
            }
        }
        return ans;
    }
}

31. 下一个排列

题目链接:点击跳转至本题

题目大意
给你一个数组nums,要求找出数组中数字全排列后,字典序中的下一个更大的排列。如果不存在下一个更大的排列,则将数字重新排列成最小的排列。
字典序的下一个值:比如对于1、2、3来说,可以组成这样的字典序列表[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]],让我们找到这其中一组排列的下一组排列,比如[1,2,3]的全排列的下一个组合是 [1,3,2],如果到了最后一组,则将这些数字升序排列。

解题思路:倒序遍历+双指针
求全排列的下一个组合,解法是从右往左依次遍历,找到两个数,左边的数<右边的数。找到后进行交换,并且对左边数后面的数进行顺序排列,这样找到的就是全排列后的下一个数字,操作之后使用return终止程序。

class Solution {
    
    
    public void nextPermutation(int[] nums) {
    
    
        for(int left = nums.length - 2;left >= 0;left--){
    
    
            for(int right = nums.length - 1;left < right;right--){
    
    
                if(nums[left] < nums[right]){
    
    
                    //交换这两个数字
                    swap(nums,left,right);
                    //对left指针后面数排序,就是按顺序全排列的下一个
                    Arrays.sort(nums,left+1,nums.length);
                    //找到后,程序直接终止执行
                    return;
                }
            }
        }
        //若不存在下一个更大的排列时升序排列
        Arrays.sort(nums);
    }

    void swap(int[] arr,int i,int j){
    
    
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

在这里插入图片描述

33. 搜索旋转排序数组

题目链接:点击跳转至本题

题目大意
有一个有序数组被分成两半后,将后一半拼接在最前端,构成一个新的数组,给出一个目标值target。要求找出数组中此target的索引,如果数组中不存在此target,返回-1。

解题思路:二分法
将数组从中一分为二,其中一定有一个是有序的,另一个可能是有序,也能是部分有序。此时对有序部分用二分法查找,无序部分再一分为二,其中又是一个有序,另一个可能有序,可能无序。就这样循环。
①nums[left] <= nums[mid]:这种情况下前半部分有序。
如果 nums[left] <= target <nums[mid],则在前半部分找,否则去后半部分找。

②nums[left] > nums[mid]:后半部分有序。
如果 nums[mid] <target<=nums[right],则在后半部分找,否则去前半部分找。

class Solution {
    
    
public int search(int[] nums, int target) {
    
    
        int left = 0,right = nums.length - 1;
        while(left <= right){
    
    
            int mid = (left + right) / 2;
            //如果中间值就是target,直接返回
            if(nums[mid] == target){
    
    
                return mid;
            }
            //第一个值>中间值:代表后半段是有序的
            if(nums[left] > nums[mid]){
    
    
                if(nums[mid] < target && target <= nums[right]){
    
    
                    //若target在mid右边
                    left = mid + 1;
                } else{
    
    
                    //若target在mid左边
                    right = mid - 1;
                }
            //第一个值<中间值:代表前半段是有序的
            }else {
    
    
                if(nums[left] <= target && target < nums[mid]){
    
    
                    //若target在mid左边
                    right = mid - 1;
                }else{
    
    
                    //若target在mid左边
                    left = mid + 1;
                }
            }  
        }
        return -1;
    }
}

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

题目链接:点击跳转至本题

题目大意:
给定一个升序数组nums和一个目标值target,要求找出给定目标值在数组中的开始位置和结束位置。时间复杂度限制在 O ( l o g n ) O(log^n) O(logn)级别。

解题思路:
本题关键在于使用二分查找查询左右边界,每次查找时,若nums[mid]==target找到符合条件的值时,继续令指针继续向左或向右查找,结束时返回的left指针和right指针就分别是左边界和右边界。需要注意其中指针越界或结果不是目标值的情况,这两种情况要返回-1。

class Solution {
    
    
    public int[] searchRange(int[] nums, int target) {
    
    
        int[] ans = new int[2];
        ans[0] = nums_left(nums,target);
        ans[1] = nums_right(nums,target);
        return ans;
    }

    int nums_left(int[] nums,int target){
    
    
        int left = 0,right = nums.length - 1;
        while(left <= right){
    
    
            int mid = (left + right) / 2;
            if(nums[mid] == target){
    
    
                //找到一个target后继续向左找
                right = mid - 1;
            } else if(target < nums[mid]){
    
    
                right = mid - 1;
            } else if(nums[mid] < target){
    
    
                left = mid + 1;
            }
        }
        //left越界或中值不是目标值
        if (left >= nums.length || nums[left] != target){
    
    
            return -1;
        }
        return left;
    }

    int nums_right(int[] nums,int target){
    
    
        int left = 0,right = nums.length - 1;
        while(left <= right){
    
    
            int mid = (left + right) / 2;
            if(nums[mid] == target){
    
    
                //找到一个target后继续向右找
                left = mid + 1;
            } else if(target < nums[mid]){
    
    
                right = mid - 1;
            } else if(nums[mid] < target){
    
    
                left = mid + 1;
            }
        }
        //right越界或中值不是目标值
        if (right < 0 || nums[right] != target){
    
    
            return -1;
        }
        return right;
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_43691058/article/details/107145046