代码随想录算法学习第一天|704. 二分查找,27. 移除元素,977.有序数组的平方,209.长度最小的子数组,59.螺旋矩阵II | 75.颜色分类

本文介绍了Java编程中的二分查找算法、移除元素的双指针法、有序数组平方问题的优化解法、长度最小子数组的滑动窗口算法,以及颜色分类和至少有k个重复字符的最长字符串问题。这些算法展示了在不同场景下对数组操作和IT技术的应用。
摘要由CSDN通过智能技术生成

704.二分查找

简单题,使用常见的二分查找即可,注意while的判断条件为start<=end即可 

public int search(int[] nums, int target) {
    int len = nums.length;
    int start = 0;
    int end = len - 1;
    while (start <= end) {
        int mid = (start + end) / 2;
        if (nums[mid] == target) return mid;
        if (target < nums[mid])
            end = mid - 1;
        if (target > nums[mid])
            start = mid + 1;
    }
    return -1;
}

时间复杂度:O(logn)

空间复杂度:O(1)

27.移除元素

使用暴力解法的话,一个for循环遍历数组,另一个for循环将当前要删除的元素后面的元素往前移动,推荐使用双指针解法。

fast指针遍历数组中的元素,也可以理解为寻找新数组中的元素。

新数组是指删除掉全部值为val的元素的数组

slow指针指向新数组最后一个元素的下一个位置,理解为指向更新位置。循环里面逻辑如下:

如果nums不等于val,则将nums[fast]赋值给nums[slow],然后slow指针后移,最后slow会等于新数组长度。

    public int removeElement(int[] nums, int val) {
        //新数组是删除掉所有等于val的数之后的数组
        int slow = 0; //slow指向新数组的最后一个元素的下一个元素
        for (int fast = 0; fast < nums.length; fast++) { //fast用来搜索新数组的元素
            if (nums[fast] != val) {
                nums[slow] = nums[fast];
                slow++;
            }
        }
        return slow;
    }

 977.有序数组的平方

最简单的方法,对数组中的每个元素进行平方后对整个数组进行快排,快排可以调用java的Arrays.sort(),但是时间复杂度是O(n + nlogn),达不到要求。

使用双指针法,关键思想:新建立一个新数组result存放排好序的元素,因为元素是非降序的,所以数组平方的最大值就在数组的两端,不是最左边就是最右边,不可能是中间

i指向原数组的左端,j指向原数组的右端,k指向新数组的末尾。

如果nums[i] * nums[i] > nums[j] * nums[j],则result[k] = nums[i] * nums[i],i往右移,k往左移。否则result[k] = nums[j] * nums[j],j往左移,k往左移。

注意整个while循环的判断条件为k>=0。

    public int[] sortedSquares(int[] nums) {
        //关键思想:原来数组是非降序的情况下,数组的平方的最大值一定是在数组的两侧,不是在左边就是在右边
        int i = 0, j = nums.length - 1, k = nums.length - 1;
        int[] result = new int[nums.length];
        while (k >= 0) {
            if (nums[i] * nums[i] > nums[j] * nums[j]) {
                result[k--] = nums[i] * nums[i];
                i++;
            } else {
                result[k--] = nums[j] * nums[j];
                j--;
            }
        }
        return result;
    }

209.长度最小的子数组

直接使用暴力的解法,时间复杂度为O(n^2),达不到要求的O(n)。思路是使用2个for循环寻找所有符合条件的子数组,i代表子数组的起始位置,j代表子数组的结尾,当前起始位置找到后直接break找下一个起始位置(因为要求的是最短子数组)。代码如下:

    public int force(int target, int[] nums) {
        int result = Integer.MAX_VALUE; //用来存储最终的返回结果
        int subLen = 0; //子数组的长度
        int sum = 0;
        for (int i = 0; i < nums.length; i++) {
            sum = 0; //每次开始寻找新的子数组都要先将sum置为0
            for (int j = i; j < nums.length; j++) {
                sum += nums[j];
                if (sum >= target) { //符合条件
                    subLen = j - i + 1; //计算子数组的长度
                    result = subLen < result ? subLen : result;
                    break; //符合条件的话当前其实位置不用再继续往下找了,因为求的是最短子数组
                }
            }
        }
        return result == Integer.MAX_VALUE ? 0 : result;
    }

 O(n)级别的算法使用滑动窗口算法,难点在于滑动窗口起点和终点的更新(最外层的for循环是end还是start)。

起点start:如果sum >= target,start就要往后移(滑动窗口该缩小了)

终点end:遍历整个数组,即最外层的for循环的指针。

更新start的时候使用的是while不是if,因为对于[1,1,1,1,100]的这种情况,start可能在固定end的情况下连续后移

start在移动之前,需要在sum减去当前位置的值,即sum-=nums[start]。

    public int minSubArrayLen(int target, int[] nums) {
        int i = 0, j;
        int sum = 0;
        int result = Integer.MAX_VALUE;
        int subLen = 0;
        for (j = 0; j < nums.length; j++) {
            sum += nums[j];
            while (sum >= target) { //注意这里是要持续性地将i向后移动,所以使用while而不用if
               subLen = j - i + 1;
               result = subLen < result ? subLen : result;
               sum -= nums[i]; //在往后移动的时候还要减去之前位置的值
               i++;
            }
        }
        return result == Integer.MAX_VALUE ? 0 : result;
    }

59.螺旋矩阵Ⅱ

主要是模拟这个打印过程,属于模拟类型的题目,关键在于处理开区间和闭区间。按习惯使用左闭右开。按下图进行理解。

分析知道要转n/2圈,如果n是奇数,还会存在中心点需要额外处理。先定义几个变量:

startx,starty:每一圈的起始点,初始为(0,0),进入下一圈要加一。

count:用来计数,1~n^2。

i,j:索引,用来遍历每个点。

offset:每一圈的偏移量,例如第一圈上面的边要去掉最后一个位置,所以第一圈的offset为1,进入下一圈要加一。

难点在于for循环的判断条件。因为有上下左右4个方向,所以是一个while循环里面嵌套4个for循环。

    public int[][] generateMatrix(int n) {
        int[][] nums = new int[n][n];
        int loop = n / 2;   //要转多少圈
        int startx = 0, starty = 0; //每个loop的起始点
        int offset = 1; //每圈的偏移量,例如第一圈上面的边要去掉最后一个节点,所以offset为1
        int count = 1; //用来计数,1~n^2
        int i, j;
        while (loop-- != 0) {
            //处理上面
            for (j = starty; j < n - offset; j++)
                nums[startx][j] = count++;
            //处理右边
            for (i = startx; i < n - offset; i++)
                nums[i][j] = count++;
            //处理下面
            for (; j > starty; j--)
                nums[i][j] = count++;
            //处理左边
            for (; i > startx; i--)
                nums[i][j] = count++;
            offset++;
            startx++;
            starty++;
        }
        if (n % 2 == 1) {
            nums[startx][starty] = count;
        }
        return nums;
    }

75.颜色分类

单指针法,两个for循环,第一个for循环将0交换到前面第二个for循环将1交换到前面,剩下后面的都是2。ptr指向头部(第一次头部是全为0的数组,第二次是全为1的数组),每次交换后ptr要往后移。 

    public void sortColors(int[] nums) {
        int ptr = 0; //指向头部,第一个for循环头部是全为0数组,第二个是全为1的数组
        int temp;
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] == 0) {
                //将为0的元素交换到头部
                temp = nums[ptr];
                nums[ptr] = nums[i];
                nums[i] = temp;
                //ptr往后移动
                ptr++;
            }
        }
        for (int i = ptr; i < nums.length; i++) {
            if (nums[i] == 1) {
                //将为1的元素交换到头部
                temp = nums[ptr];
                nums[ptr] = nums[i];
                nums[i] = temp;
                //ptr右移
                ptr++;
            }
        }
    }

395.至少有k个重复字符的最长字符串

这道题在leetcode的题解上使用两种解法,分别为分治和滑动窗口,这里使用了滑动窗口。滑动窗口实现的逻辑较为复杂,建议还是使用分治。

关键在于如何枚举子串(即滑动窗口)。最简单的就是枚举所有的子串然后判断它们是否符合条件,当然会超时。

最外层的for循环,遍历该字符串可能包含的字符种类t。因为有26个英文字母,所以使用遍历的范围为1~26。

for循环里面要定义以下几个变量:

left,right:子串的左右边界,初始化为0;

cnt:一个记录子串中每种字符的出现次数的数组,长度为26;

total:子串内部的字符种类数目;

less:当前子串出现次数<k的字符数量。

在for循环内部,使用两个while循环来遍历right和left,遍历left的while是嵌套在right里面的

第一个while:当right<原字符串的长度。

第二个while:当子串内部的字符种类数目 > t。

每次移动right和left都要更新cnt(子串中每种字符的出现次数)

对于移动right的情况:

如果当前右边界对应的字符出现次数 == 1(某个字符出现的次数从0->1),则total++(代表有新字符出现在该子串),less++(这里不是很明白)。

如果当前右边界对应的字符出现次数 == k (某个字符出现的次数从k-1->k,则less--(不再小于k)。

移动左边界也是类似的逻辑。left的while循环的判断条件是total > t,因为这样对于任何一个right,都可以找到lmin,使得s[lmin...right]之间的字符种类数目不大于t(跳出while后total <= t,这里想一下应该可以明白了)。

    public int longestSubstring(String s, int k) {
        int result = 0; //记录最终返回的结果
        int len = s.length();
        for (int t = 1; t <= 26; t++) {
            int left = 0, right = 0;
            int[] cnt = new int[26]; //cnt代表子串每种字符的出现次数,使用一个数组来记录,英文字母有26个所以长度为26
            int total = 0, less = 0; //total代表子串内部的字符种类数目,less代表当前出现次数<k的字符数量
            while (right < len) { //第一个while,移动right
                cnt[s.charAt(right) - 'a']++; //更新cnt,出现次数加一
                //对于移动右边界right的情况而言
                if (cnt[s.charAt(right) - 'a'] == 1) {
                    total++; //如果出现次数为1,表示有新字符出现,total要加一
                    less++; //小于k的字符数量+1(不明白)
                }
                if (cnt[s.charAt(right) - 'a'] == k) {
                    less--; //等于k表示不再小于k,所以要-1
                }

                while (total > t) { //第二个while,移动left
                    cnt[s.charAt(left) - 'a']--;
                    if (cnt[s.charAt(left) - 'a'] == k - 1) {
                        less++;
                    }
                    if (cnt[s.charAt(left) - 'a'] == 0) {
                        total--;
                        less--;
                    }
                    left++; //left右移
                }
                if (less == 0) { //如果该子串符合条件
                    result = Math.max(result, right - left + 1);
                }
                right++; //right右移
            }
        }
        return result;
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值