力扣刷题记录--技巧题


普通技巧题

LeetCode 31. 下一个排列

2024.05.15 一刷

原题链接

思路:https://leetcode.cn/problems/next-permutation/solutions/80560/xia-yi-ge-pai-lie-suan-fa-xiang-jie-si-lu-tui-dao-/?envType=study-plan-v2&envId=top-100-liked

代码如下:

class Solution {
    public void nextPermutation(int[] nums) {
        int len = nums.length;
        for (int i = len - 1; i > 0; i--) {
            if (nums[i] > nums[i - 1]) {
                Arrays.sort(nums, i, len);
                for (int j = i; j <len; j++) {
                    if (nums[j] > nums[i - 1]) {
                        int temp = nums[j];
                        nums[j] = nums[i - 1];
                        nums[i - 1] = temp;
                        return;// 找到下一个更大的序列,直接返回
                    }
                }
            }
        }
        // 如果不存在下一个更大的排列,则将数字升序排列
    	Arrays.sort(nums);
		return;  
    }
 }

LeetCode 287. 寻找重复数

原题链接

2024.05.16 二刷

思路:

数组长度为n+1,数字大小范围在1~n,所以至少有一个数重复,题目指定只有一个重复的数;
也就是这个数可能重复好多次,而其它数字只会在数组中出现一次。

标记法:
每次遍历到一个数,将它的值作为下标(index),找到这个下标对应数组中的位置;
我们需要把这个位置上的数(nums[index])做一个标记,表示下标对应的值已经出现过一次了;
可以将nums[index]转为对应的相反数;
那么后面如果再遍历到相同的值,将值作为下标,找到在数组中的位置,查看这个位置上的数是不是负数;
如果是负数,说明这个值之前已经出现过一次了,那就返回下标;
如过不是负数,就正常将值作为下标,找到数组中对应位置,将这个位置的值改为负数;

需要特别注意的是,由于会修改nums[index]为负数,而后面还有可能会遍历到这个负数,
需要根据这个负数作为下标去判断数组中的对应位置的值,就会出现数组越界异常,
所以每次遍历到一个数的时候,先将这个数进行绝对值转换,避免数组越界;

代码如下:

class Solution {
    public int findDuplicate(int[] nums) {
        int len = nums.length;
        for(int num : nums){
            int index = Math.abs(num);
            if(nums[index]<0){
                return index;
            }
            nums[index] = -nums[index];
        }
        return len;// n+1
    }
}

荷兰国旗问题(三色排序)

LeetCode 75. 颜色分类

原题链接

2024.05.12 一刷

思路:类似快排分区交换元素的思想

用三个指针:p0,i,p2用来进行划分区间,其中:

  • [0,p0)范围内都是0元素,也就是p0左边(不包括p0),全是0;
  • [p0,i)范围内都是1元素,也就是p0到i范围(不包括i)全是1;
  • (p2,len-1]范围内都是2元素,也就是p2右边(不包括p2)到len-1处都是2;

i用来遍历nums数组;

初始化:要保证三个区间一开始都是空区间

  • p0=0;
  • i=0;
  • p2=len-1;

while循环退出条件:当i遍历到p2时,由于p2右边(不包括p2)到len-1处都是2,
i到p2处还需要判断下nums[p2]的情况,所以当i<=p2,都在while内,当i超过p2就退出循环;

代码如下:

class Solution {
    public void sortColors(int[] nums) {
        int len = nums.length;
        if(len==1){
            return;
        }
        int p0=0;
        int i=0;
        int p2=len-1;
        while(i<=p2){
            // [0,p0)
            // [p0,i)
            // (p2,len-1]
            if(nums[i]==0){
                swap(nums,p0,i);// 将为0的交换到p0左边区域内
                p0++;// 因为p0左边(不包括p0)是0,要维持定义
                i++;// i需要继续遍历下一个位置
            }else if(nums[i]==1){
                i++;// 1本来就在中间,不需要调整位置,p0和p2的位置也不需要向右或向左
            }else{// nums[i]==2
                swap(nums,i,p2);
                p2--;
                // 这里i不能+1,因为p2位置交换回来的的是什么数并不知道,
                // 还需要对交换完的nums[i]进行判断
            }
        }
    }
    private void swap(int[] nums,int index1,int index2){
        int tmp = nums[index1];
        nums[index1] = nums[index2];
        nums[index2] = tmp;
    }
}

摩尔投票

Boyer-Moore 投票算法主要用于查找数组中出现次数超过一半的主要元素,核心思想是通过消除不同的元素对来找到主要元素,这个算法的时间复杂度为 O(n),其中 n 是数组的长度。

  • 初始化两个变量 candidate 和 count,其中 candidate 用于保存候选主要元素,count
    用于记录候选主要元素出现的次数。初始时,candidate 可以是任何数组中的元素,而 count 初始化为0。

  • 遍历数组中的每个元素:

    • 如果 count 等于0,将当前元素设置为候选主要元素 candidate,并将 count 设置为1。
    • 如果 count 不等于0,检查当前元素是否等于 candidate:
      • 如果相等,将count递增1,表示找到了一个与候选主要元素相同的元素。
      • 如果不相等,将 count递减1,表示找到了一个与候选主要元素不同的元素。
  • 在遍历完成后,candidate 变量中存储的元素就是数组中的主要元素。

169. 多数元素

原题链接

思路:
1.哈希表统计:时间O(n),空间O(n)

  • 遍历数组,hashmap统计各数字的数量,遍历hashmap,找出最大的value对应的key;

2.数组统计:时间O(nlogn),空间O(logn)

  • 给数组进行排序,数组中间元素一定为众数

3.摩尔投票法:时间O(n),空间O(1)

  • 众数计票为+1,非众数计票为-1,所有数字的票数和>0;
  • 若数组的前a个数字的票数和=0(众数和非众数抵消了),则数组nums后面的(n−a)个数中一定还是众数占多数;
  • 一个很重要的点—>票数和为0的时候:一定是众数和非众数抵消了,那么就不用管前面抵消的部分了。
  • 直接假设剩下的数组的第一个数字n1为众数mode(如果第一个数字n1不是众数,出于众数在本题中的定义,在遍历完数组之前,一定会出现计票为0的情况,而剩下的部分肯定还有众数,那么继续将剩下的部分的第一个数字假设为众数)。
  • 这样只要不断假设数组剩余部分的第一个数字为众数进行计票,不是众数就一定会出现计票为0的情况,就可以缩小寻找众数的区间;
  • 这样最后剩下的区间的第一个元素一定为众数;

代码如下:

class Solution {
    public int majorityElement(int[] nums) {
        int mode = 0;// 众数
        int votes = 0;// 投票计数
        for(int num:nums){
            // 投票为0,说明前面非众数与众数相互抵消,将剩余区间的第一个数选为众数
            if(votes == 0)mode = num;// 或者初始化众数
            if(num == mode){
                votes++;
            }else{
                votes--;
            }
        }
        return mode;
    }
}

位运算技巧

一、n&(n-1)

n&(n-1)作用将n的二进制表示中的最低位为1的改为0。

一个简单的例子:

  • n = 10100(二进制),最低位的1是10100(加粗下划线处),
  • n-1 = 10011 ,为了能够-1,n的二进制位需要向其最低位的1借位,所以n-1会将最低位的1变成0。

可能会有人问:那10011(n-1)不是比10100(n)还多弄出了一个1吗?所以这时候就需要“&”运算的参与了。“n-1”所造成的多出的1,在和“n”相“&”之后,一定会被消除为0,因为其之所以能多出1,一定是因为原先的“n”在对应位置上为0,借位不得而成的1。

  • n&(n-1) = 10000
    可以看到原本最低位为1的那位变为0。

应用:

1. 求一个数的二进制表示中的1的个数

while(n>0){
	count ++;
    n&=(n-1);
}

例题:

力扣 191. 位1的个数

原题链接

在这里插入图片描述

代码如下

public class Solution {
    // you need to treat n as an unsigned value
    public int hammingWeight(int n) {
        int count=0;
        while(n!=0){
            n&=(n-1);
            count++;
        }
        return count;
    }
}
AcWing 801. 二进制中1的个数

原题链接
在这里插入图片描述
代码如下:

import java.util.Scanner;

public class Main{
    public static void main(String[] args){
        Scanner sc =new Scanner(System.in);
        int n=sc.nextInt();
        int[] a=new int[n];
        for(int i=0;i<n;i++)a[i]=sc.nextInt();
        
        for(int i=0;i<n;i++){
            int count=0;
            while(a[i]>0){
                a[i]&=a[i]-1;
                count++;
            }
            a[i]=count;
        }
        for(int i=0;i<n;i++){
            System.out.print(a[i]+" ");
            
        }
    }
}

2. 判断一个数是否是2的方幂

n > 0 && ((n & (n - 1)) == 0 )

解释((n & (n-1)) == 0):

如果A&B==0,表示A与B的二进制形式没有在同一个位置都为1的时候。

那么本题到底啥意思??

不妨先看下n-1是什么意思。

令:n=1101011000(二进制,十进制也一样),则

n-1=1101010111。

n&(n-1)=1101010000

由此可以得出,n和n-1的低位不一样,直到有个转折点,就是借位的那个点,从这个点开始的高位,n和n-1都一样,如果高位一样这就造成一个问题,就是n和n-1在相同的位上可能会有同一个1,从而使((n & (n-1)) != 0),如果想要

((n & (n-1)) == 0),则高位必须全为0,这样就没有相同的1。

所以n是2的幂或0。


二、n&(~n+1)或n&-n

n&-n或n&(~n+1)的作用: 保留二进制下最后出现1的位置的数字,其余位置置0;

例题:

AcWing 801. 二进制中1的个数

原题链接
在这里插入图片描述
代码如下:


import java.util.Scanner;

public class Main{
    // lowbit函数只保留n的二进制最低位的1
    public static int lowbit(int n){
        return n&-n;
        //return n&((~n)+1);
    }
    public static void main(String[] args){
        Scanner sc =new Scanner(System.in);
        int n=sc.nextInt();
        int[] a=new int[n];
        for(int i=0;i<n;i++)a[i]=sc.nextInt();
        
        for(int i=0;i<n;i++){
            int count=0;
            while(a[i]>0){
                a[i]-=lowbit(a[i]);//把二进制最低位的1减去,能减多少次,其二进制就有多少个1
                count++;
            }
            a[i]=count;
        }
        for(int i=0;i<n;i++){
            System.out.print(a[i]+" ");
            
        }
    }
}

三、n>>k&1

n>>k&1的作用:可以算出n的二进制表示中,从低到高第k位是0还是1(最低位为第0位)。

举例:n=10,二进制表示:1010,假设求第3位(从0开始)是多少?

n>>3=0001,0001&1=1,所以第三位是1(1010)


四、 ^ 操作(异或)

1.交换两个数

原理:

  • a^a=0;
  • a^0=a;
  • a^b = b^a;交换律
  • a ^b ^c =a^ (b^ c)=(a ^b )^ c;结合律
  • 所以a^ b^a=b。
LeetCode 344. 反转字符串

原题链接

对字符数组内容进行翻转,左右指针向中间遍历,交换首尾指针指向的字符。交换采用异或操作,可以不使用额外变量。

代码如下:

class Solution {
    public void reverseString(char[] s) {
         int l=0,r=s.length-1;
         while(l<r){
             s[l]^=s[r];//s[l]=s[l]^s[r]
             s[r]^=s[l];//s[r]=s[r]^s[l]=s[r]^(s[l]^s[r])=s[l]
             s[l]^=s[r];//s[l]=s[l]^s[r]=(s[l]^s[r])^s[l]=s[r]
             l++;
             r--;
         }
    }
}

LeetCode 189. 轮转数组

原题链接

2023.06.03 二刷

题解区里看到有人引用国外的一个短小精悍的题解:
示例:
nums = “----->–>”; k =3
result = “–>----->”;

过程
reverse “----->–>” we can get “<–<-----”
reverse “<–” we can get “–><-----”
reverse “<-----” we can get “–>----->”

代码如下:

class Solution {
    public void rotate(int[] nums, int k) {
        int n=nums.length;
        k%=n;//k可能比nums大,但是nums右移n位还是原来的nums
        reverse(nums,0,n-1);//反转区间两端都为闭
        reverse(nums,0,k-1);
        reverse(nums,k,n-1);
    }
    //对数组指定区间进行反转
    public void reverse(int[] nums,int l,int r ){
        while(l<r){
            // 基于异或运算的交换律和结合律,以及a^a=0,a^0=a;
            nums[l]^=nums[r];//nums[l]=nums[l]^nums[r]
            nums[r]^=nums[l];//nums[r]=nums[r]^nums[l]^nums[r]=nums[l]
            nums[l]^=nums[r];//nums[l]=(nums[l]^nums[r])^nums[l]=nums[r]
            l++;
            r--;
        }
    }
}

2.找到只出现一次的数

LeetCode 136. 只出现一次的数字

原题链接

给你一个 非空 整数数组 nums ,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

2024.05.10 二刷

思路:异或运算

需要知道几个异或的性质:

  • 任何数和 0 做异或运算,结果仍然是原来的数,比如a^0=a;
  • 任何数和其自身做异或运算,结果是0,比如a^a=0;
  • 异或运算满足交换律和结合律,比如aba=baa=b(aa)=b^0=b;

知道这几个性质就很容易想到方法了:

  • 只需要最开始定义一个res=0,然后把nums中每一个数都和res进行异或,
  • 出现两次的数在异或之后,采用交换律和结合律,会变成0,
  • 0和原来的res=0异或还是0,那么只出现一次的那个数和res异或之后就是那个只出现一次的数了。

代码如下:

class Solution {
    public int singleNumber(int[] nums) {
        int res=0;
        for(int num:nums){
            res ^= num;
        }
        return res;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值