【leetcode】数组

总结

前缀和技巧

【参考:前缀和技巧:解决子数组问题__微信公众号

前缀和主要适用的场景是原始数组不会被修改的情况下,频繁查询某个区间的累加和

题目:560. 和为 K 的子数组

【参考:小而美的算法技巧:前缀和数组 :: labuladong的算法小抄


简单

27. 移除元素

参考:27. 移除元素 - 力扣(LeetCode)

双指针法(快慢指针法)

  • 拷贝覆盖

元素的顺序可以改变

class Solution {
    public int removeElement(int[] nums, int val) {
        int n = nums.length;
        int left = 0;
        for (int right = 0; right < n; right++) {
            if (nums[right] != val) {
                nums[left] = nums[right]; // 拷贝覆盖
                left++;
            }
        }
        return left; // 返回移除后数组的新长度
    }
}

// 双指针优化(尽可能少拷贝覆盖)
class Solution {
    public int removeElement(int[] nums, int val) {
        int left = 0;
        int right = nums.length;
        while (left < right) {
            if (nums[left] == val) {
                // 元素的顺序可以改变 所以可以随意点
                nums[left] = nums[right - 1]; // 交换移除
                right--;
            } else {
                left++;
            }
        }
        return left;
    }
}

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

【参考:26. 删除有序数组中的重复项 - 力扣(LeetCode)

【参考:【双指针】删除重复项-带优化思路 - 删除有序数组中的重复项 - 力扣(LeetCode)

元素的顺序不可以改变

class Solution:
    def removeDuplicates(self, nums: List[int]) -> int:
        if len(nums)<=1:
            return len(nums)
            
        slow=0
        fast=0       
        while fast<len(nums):
            if nums[slow]!=nums[fast]:
                slow+=1 # 下一个数字的下标,因为这个数字本身就需要留一个
                nums[slow]=nums[fast]
            fast+=1
    
        return slow+1

class Solution:
    def removeDuplicates(self, nums: List[int]) -> int:
        if len(nums)<=1:
            return len(nums)

        slow=0
        fast=0       
        while fast<len(nums):
            if nums[slow]!=nums[fast]:
                if fast-slow>=2: # 有相同的元素nums[slow] nums[slow+1]
                    nums[slow+1]=nums[fast] # 拷贝覆盖
                slow+=1
            fast+=1
    
        return slow+1

80. 删除有序数组中的重复项 II ??

【参考:80. 删除有序数组中的重复项 II - 力扣(LeetCode)

【参考:[一招秒杀所有同类型题] 非常容易理解的双指针 - 删除有序数组中的重复项 II - 力扣(LeetCode)

有点没有理解透彻的感觉 待定

class Solution:
    def removeDuplicates(self, nums: List[int]) -> int:
        if len(nums)<=1:
            return len(nums)

        left=1 # 从第二个元素开始
        for right in range(2,len(nums)):
            # nums[right] 与前两个数相比
            # 如果全部相等, 说明right指向的元素是重复元素,不保留
            if nums[right]==nums[left] and nums[right]==nums[left-1]:
                continue

            # 此时right指向的是非重复元素
            left+=1
            nums[left]=nums[right]
            
        
        return left+1

中等

209. 长度最小的子数组

参考:209. 长度最小的子数组 - 力扣(LeetCode)

滑动窗口

class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        if(nums.length==0||nums==null) 
            return 0;
        int i=0; // 滑动窗口的起始位置i,结束位置j
        int result=nums.length+1; // 返回的结果 初始为一个比nums.length大的值即可
        int total=0; // 滑动窗口的数值之和
        for(int j=0;j<nums.length;j++){
            total+=nums[j];
            while(total>=target){
                int subLength=j-i+1; // 滑动窗口的长度
                result=Math.min(result,subLength); // 取最小值
                // total去除nums[i],滑动窗口从nums[i+1]开始
                total-=nums[i]; 
                i++;
            }
        }
        if(result==nums.length+1)
            return 0;
        else
            return result; // 返回其长度

    }
}

59. 螺旋矩阵 II

参考:Spiral Matrix II (模拟法,设定边界,代码简短清晰) - 螺旋矩阵 II - 力扣(LeetCode)

class Solution {
    public int[][] generateMatrix(int n) {
        int l = 0, r = n - 1, t = 0, b = n - 1;
        int[][] result = new int[n][n];
        int num = 1, tar = n * n;
        while(num <= tar){
            for(int i = l; i <= r; i++) result[t][i] = num++; // left to right.
            t++;
            for(int i = t; i <= b; i++) result[i][r] = num++; // top to bottom.
            r--;
            for(int i = r; i >= l; i--) result[b][i] = num++; // right to left.
            b--;
            for(int i = b; i >= t; i--) result[i][l] = num++; // bottom to top.
            l++;
        }
        return result;
    }
}


560. 和为 K 的子数组 ***

【参考:560. 和为 K 的子数组 - 力扣(LeetCode)

int n = nums.length;
// 前缀和数组
int[] preSum = new int[n + 1];
preSum[0] = 0;
for (int i = 0; i < n; i++)
    preSum[i + 1] = preSum[i] + nums[i];
    
preSum[i+1] 就是 nums[0..i] 的和,
如果想求 nums[i..j] 的和,只需要 preSum[j+1] - preSum[i] 即可

比如 nums[3,4,5] =-2+4+1=3
preSum[5+1]-preSum[3]=13-10=3
在这里插入图片描述

  • 解法一
    在这里插入图片描述
    无法通过
class Solution {
    public int subarraySum(int[] nums, int k) {
        int n=nums.length;
        int[] preSum=new int[n+1];
        preSum[0]=0;

        for(int i=0;i<n;i++){
            preSum[i+1]=preSum[i]+nums[i];
        }

        int result=0;
        for(int i=0;i<n;i++){
            for(int j=0;j<n;j++){
                if(preSum[j+1]-preSum[i]==k){
                    result++;
                }
            }
        }

        return result;
    }
}

分清 sum0_i , sum0_j的范围

class Solution {
    public int subarraySum(int[] nums, int k) {
        int n = nums.length;
        // map:前缀和,该前缀和出现的次数
        HashMap<Integer, Integer> preSum = new HashMap<>();
        // base case
        preSum.put(0, 1);

        int res = 0, sum0_i = 0;
        for (int i = 0; i < n; i++) {
            sum0_i += nums[i];
            // 这是我们想找的前缀和 nums[0..j]
            int sum0_j = sum0_i - k;
            // 如果前面有这个前缀和,则直接更新答案
            if (preSum.containsKey(sum0_j))
                res += preSum.get(sum0_j);
            // 把前缀和 nums[0..i] 加入并记录出现次数
            preSum.put(sum0_i,
                    preSum.getOrDefault(sum0_i, 0) + 1);
        }
        return res;
    }
}

逐步扩大范围避免重复求和

class Solution {
    public int subarraySum(int[] nums, int k) {
        int n=nums.length;
    
        int result=0;
        for(int end=0;end<n;end++){
            int sum=0;
            for(int start=end;start>=0;start--){ // [0..start..end]
                // sum=num[start..end]; start:0 <- end 子数组范围逐步扩大
                // 如果知道 [start,end]子数组的和,就能直接推出[start-1,end]的和
                sum += nums[start]; 
                if(sum==k){
                    result++;
                }
            }
        }
        return result;
    }
}

380. O 时间插入、删除和获取随机元素

【参考:380. O 时间插入、删除和获取随机元素 - 力扣(LeetCode)

【参考:O 时间插入、删除和获取随机元素 - O(1) 时间插入、删除和获取随机元素 - 力扣(LeetCode)
【参考:常数时间删除/查找数组中的任意元素 :: labuladong的算法小抄

class RandomizedSet {
    List<Integer> nums;
    // 存放nums中的val:index
    Map<Integer,Integer> valToIndex;
    Random random; 

    public RandomizedSet() {
        nums=new ArrayList<>();
        valToIndex=new HashMap<>();
        random=new Random();
    }
    
    public boolean insert(int val) {
        // 已经存在val
        if(valToIndex.containsKey(val)) return false;
        nums.add(val);
        valToIndex.put(val,nums.size()-1);
        return true;
    }
    // 每次删除转化为删除数组最后一个元素(题目没说要保持数组内元素有序)
    public boolean remove(int val) {
        // 不存在val
        if(!valToIndex.containsKey(val)) return false;
        // 将last覆盖index处的val值(相当于变相删除了val),
        int index=valToIndex.get(val);
        int last=nums.get(nums.size()-1);
        nums.set(index,last);
        // 再删除nums最后一个元素,并更新valToIndex
        nums.remove(nums.size()-1);
        valToIndex.put(last,index);        
        valToIndex.remove(val);
        return true;
    }
    
    public int getRandom() {
        int i=random.nextInt(nums.size());
        return nums.get(i);
    }
}

/**
 * Your RandomizedSet object will be instantiated and called as such:
 * RandomizedSet obj = new RandomizedSet();
 * boolean param_1 = obj.insert(val);
 * boolean param_2 = obj.remove(val);
 * int param_3 = obj.getRandom();
 */

困难

710. 黑名单中的随机数

【参考:710. 黑名单中的随机数 - 力扣(LeetCode)

【参考:详解官方思路,慢慢学会map - 黑名单中的随机数 - 力扣(LeetCode)

 //按官方给的思路,逻辑上构思一个0~n的整型数组,而我们把随机边界bound就设为(n - blacklist.length)
 //也就是我们逻辑上认为0~n的所有整数中,刚好最后blacklist.length个数全是黑名单数
 //这样我们只需要在0~bound上随机一次,就能得到一个可取随机白名单数
 //那实际上不可能刚好黑名单数就全出现在最后
 //所以我们要是可以把在前边出现的黑名单数,都一一映射到后边的非黑名单数的话,那就相当于实现了这样的效果了
class Solution {
    //随机对象
    Random random;
    //随机边界,即构思的0~n大整型数组,前边全白名单,后边全黑名单数的分界线
    public int bound;
    //存放前面出现黑名单,通过键值就能一一映射到后边非黑名单的map集合
    public Map<Integer,Integer> blaceToWhite; // num:index

   
    public Solution(int n, int[] blacklist) {
        //初始化随机对象
        this.random=new Random();
        //初始化随机边界
        this.bound=n-blacklist.length;
        //初始化map
        //前面的键作为前面的数本身,若随机到的此数不是黑名单数,直接返回这个数
        //后面的值作为他映射到的后边的非黑名单数,若随机到的此数是黑名单数,则返回这个映射值
        blaceToWhite = new HashMap<Integer,Integer>();

        
        
        //那么如何把边界前面出现的黑名单数,一一映射到边界后边的非黑名单数去呢? 
        //考虑到边界前面有多少个黑名单数,边界后边就会有多少个非黑名单数
        
        //将边界后的黑名单数记录下
        Set<Integer> backBlack = new HashSet<Integer>();
        for(int x : blacklist){
            //比如x拿到50,既表示它数本身是50,但同时也表示对应在0~n的大数组中,它的下标也是50
            //故x > bound可以表示x的下标位置在边界后,即将它添加到边界后黑名单集合set中
            if(x >= bound){
                backBlack.add(x);
            }
        }
       
        //开始将边界前的黑名单数数映射到边界后的非黑名单数
        int w = bound;
        for(int x : blacklist){
            //同理,它表示边界前的黑名单数
            if(x < bound){
                //去找边界后的非黑名单数
                //从边界数往后依次判断,若此数是边界后黑名单数,那向后继续找
                while(backBlack.contains(w)){
                    w++;
                }
                //直到找到边界后的一个非黑名单数
                //建立映射
                blaceToWhite.put(x,w);
                //让下一个映射,往后到下一个数
                w++;
            }
        }
        

    }
    
    public int pick() {
        //如果边界前随机的就是白名单数,直接返回就完了,否则返回这个边界前黑名单数对应的边界后映射

        //map方法:default V getOrDefault​(Object key, V defaultValue) 
        //返回指定键映射到的值,如果此映射不包含该键的映射,则返回 defaultValue 
        int x = random.nextInt(bound);
        return blaceToWhite.getOrDefault(x, x); // 没看懂
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值