剩余——11、29、31、33、41、42、45、46、63、64

11、二进制中1的个数

要善用位运算

若 n & 1 = 0n&1=0 ,则 nn 二进制 最右一位 为 00 ;
若 n & 1 = 1n&1=1 ,则 nn 二进制 最右一位 为 11

public class Solution {
    public int hammingWeight(int n) {
        int res = 0;
        while(n != 0) {
            res += n & 1;
            n >>>= 1;
        }
        return res;
    }
}

29、最小的K个数

题解——主要是堆排序

快速排序方法题解

Arrays.sort(array);
//会检查数组个数大于286且连续性好就使用归并排序,若小于47使用插入排序,其余情况使用双轴快速排序;
//默认升序排序

(i1, i2) -> Integer.compare(i2, i1) ///后面的比前面的大,放到前面,也就是实现大顶堆
i2>i1——1 交换位置,大的放到前面
i2=i1——0
i2<i1——-1

(p, q) -> q - p//
后面两种本质上是一个意思, 传入2个量, 返回比较他们的结果,
q-p为+,后面比前面大,交换,维护成大顶堆

对于排序来讲,你可以认为当返回1时,指定的数和参数会进行交换,而非1时则不变,指定数可以当作原本的数组中靠前的数,而参数可以当作靠后的数,又因为只有靠后数大于靠前数时才返回1,所以小的会被放到后面,此时降序排序(方便记忆)

一般升序采用大顶堆,降序采用小顶堆)

堆排序

public int[] getLeastNumbers(int[] arr, int k) {
    if (k == 0) {
        return new int[0];
    }
    // 使用一个最大堆(大顶堆)
    // Java 的 PriorityQueue 默认是小顶堆,添加 comparator 参数使其变成最大堆
    Queue<Integer> heap = new PriorityQueue<>(k, (i1, i2) -> Integer.compare(i2, i1));

    for (int e : arr) {
        // 当前数字小于堆顶元素才会入堆
        if (heap.isEmpty() || heap.size() < k || e < heap.peek()) {
            heap.offer(e);
        }
        if (heap.size() > k) {
            heap.poll(); // 删除堆顶最大元素
        }
    }

    // 将堆中的元素存入数组
    int[] res = new int[heap.size()];
    int j = 0;
    for (int e : heap) {
        res[j++] = e;
    }
    return res;
}

标准快排

class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        quickSort(arr, 0, arr.length - 1);
        return Arrays.copyOf(arr, k);
    }
    private void quickSort(int[] arr, int l, int r) {
        // 子数组长度为 1 时终止递归
        if (l >= r) return;
        // 哨兵划分操作(以 arr[l] 作为基准数)
        int i = l, j = r;//从左右开始向中间搜索
        while (i < j) {
            while (i < j && arr[j] >= arr[l]) j--;//找到右边的  第一次小于基准l
            while (i < j && arr[i] <= arr[l]) i++;//找到左边的  第一次大于基准l
            swap(arr, i, j);//,交换i\j,使得大于基准值的到右边,小于基准值的到左边
        }
        swap(arr, i, l);//前面的递归操作,使得i=j了,也就是遍历完了,此时交换i、基准值l,
        
        // 递归左(右)子数组执行哨兵划分
        quickSort(arr, l, i - 1);
        quickSort(arr, i + 1, r);
    }
    private void swap(int[] arr, int i, int j) {//交换操作
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }
}

31、1-n整数中1出现的次数

case 1: cur=0
2 3 0 4
千位和百位可以选00 01 02…22 十位可以取到1( 形如[00|01…|22]1[0-9] 都是<2304 ) 个位可以选0-9 共有 23 * 10 中排列
当千位和百位取23,如果十位取1 那就是形如 231[0-9] > 2304,所以当千位和百位取23,十位只能能取0,个位取0-4即 2300 2301 2302 2303 2304
但是2301不应该算进来,这个1是 单独 出现在个位的(而11,121,111这种可以被算多次)
即 23*10
case 2: cur=1
2 3 1 4
千位和百位可以选00 01 02…22 十位可以取到1 个位可以选0-9 共有 23 * 10 中排列
当千位和百位取23,十位取1,个位可以去0-4 即 2310-2314共5个
即 23 *10 + 4 +1
case 3: cur>1 即2-9
2 3 2 4
千位和百位可以选00 01 02…22 十位可以取到1(形如 [00|01…|22]1[0-9] 都是<2324) 个位可以选0-9 共有 23 * 10 中排列
当千位和百位取23,十位取1,个位可以去0-9 即 2310-2319共10个 (其中2311,被计算了两次,分别是从个位和十位分析得到的1次)
即 23 *10 + 10

找规律

最好是用dp动态规划来做,但是没看懂

DP题解

    public int countDigitOne(int n) {
        if(n == 0) {
            return 0;
        }
        int digit = (int)Math.log10(n) + 1;
        //二维dp,dp[i][0]为1至i位中1的个数, 
        //dp[i][1]为最高位为9的时候0至i位1的个数。
        int[][] dp = new int[digit+1][2];
        dp[1][0] = n % 10 >= 1 ? 1:0;
        dp[1][1] = 1;
        for(int i = 2; i <= digit; i++) {
            int k = n / ((int)Math.pow(10, i-1)) % 10;
            dp[i][0] = k * dp[i-1][1] + dp[i-1][0];
            if(k == 1) {
                dp[i][0] += n % (int)Math.pow(10, i-1) + 1;
            } else if(k > 1){
                dp[i][0] +=  (int)Math.pow(10, i-1);
            }
            dp[i][1] = 10 * dp[i-1][1] + (int)Math.pow(10, i-1);
        }
        return dp[digit][0];
    }

33、丑数

三个指针,非常好的解释

经典题解

就是把所有丑数列出来,然后从小到大排序。而大的丑数必然是小丑数的2/3/5倍,所以有了那3个数组。每次就从那数组中取出一个最小的丑数归并到目标数组中。

class Solution {
    public int nthUglyNumber(int n) {
        int[] dp = new int[n];  // 使用dp数组来存储丑数序列
        dp[0] = 1;  // dp[0]已知为1
        int a = 0, b = 0, c = 0;    // 下个应该通过乘2来获得新丑数的数据是第a个, 同理b, c

        for(int i = 1; i < n; i++){
            // 第a丑数个数需要通过乘2来得到下个丑数,第b丑数个数需要通过乘2来得到下个丑数,同理第c个数
            int n2 = dp[a] * 2, n3 = dp[b] * 3, n5 = dp[c] * 5;
            dp[i] = Math.min(Math.min(n2, n3), n5);
            if(dp[i] == n2){
                a++; // 第a个数已经通过乘2得到了一个新的丑数,那下个需要通过乘2得到一个新的丑数的数应该是第(a+1)个数
            }
            if(dp[i] == n3){
                b++; // 第 b个数已经通过乘3得到了一个新的丑数,那下个需要通过乘3得到一个新的丑数的数应该是第(b+1)个数
            }
            if(dp[i] == n5){
                c++; // 第 c个数已经通过乘5得到了一个新的丑数,那下个需要通过乘5得到一个新的丑数的数应该是第(c+1)个数
            }
        }
        return dp[n-1];
    }
}

42、和为S的两个数字

双指针题解

数组的情况

class Solution {
    public int[] twoSum(int[] nums, int target) {
        int i = 0, j = nums.length - 1;//对撞双指针
        while(i < j) {
            int s = nums[i] + nums[j];//求双指针的和
            if(s < target) i++;//小,左边的数变大
            else if(s > target) j--;//大,右边的数变小
            else return new int[] { nums[i], nums[j] };//否则就是相等,返回当前这条记录
        }
        return new int[0];
    }
}

链表的情况

import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> FindNumbersWithSum(int [] array,int sum) {
        ArrayList<Integer> list = new ArrayList<Integer>();
        if (array == null || array.length < 2) {
            return list;
        }
        int i=0,j=array.length-1;
        while(i<j){
            if(array[i]+array[j]==sum){
            list.add(array[i]);
            list.add(array[j]);
                return list;
           }else if(array[i]+array[j]>sum){
                j--;
            }else{
                i++;
            }
            
        }
        return list;
    }
}

41、和为S的连续正数序列

import java.util.ArrayList;
public class Solution {
    public ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) {
        //存放结果
        ArrayList<ArrayList<Integer> > result = new ArrayList<>();
        //两个起点,相当于动态窗口的两边,根据其窗口内的值的和来确定窗口的位置和大小
        int plow = 1,phigh = 2;
        while(phigh > plow){
            //由于是连续的,差为1的一个序列,那么求和公式是(a0+an)*n/2
            int cur = (phigh + plow) * (phigh - plow + 1) / 2;
            //相等,那么就将窗口范围的所有数添加进结果集
            if(cur == sum){
                ArrayList<Integer> list = new ArrayList<>();
                for(int i=plow;i<=phigh;i++){
                    list.add(i);
                }
                result.add(list);
                plow++;
            //如果当前窗口内的值之和小于sum,那么右边窗口右移一下
            }else if(cur < sum){
                phigh++;
            }else{
            //如果当前窗口内的值之和大于sum,那么左边窗口右移一下
                plow++;
            }
        }
        return result;
    }
}

更通用的情况

public int[][] findContinuousSequence(int target) {
    int i = 1; // 滑动窗口的左边界
    int j = 1; // 滑动窗口的右边界
    int sum = 0; // 滑动窗口中数字的和
    List<int[]> res = new ArrayList<>();

    while (i <= target / 2) {
        if (sum < target) {//和比目标小
            // 右边界向右移动
            sum += j;
            j++;
        } else if (sum > target) {//和比目标大
            // 左边界向右移动
            sum -= i;
            i++;
        } else {
            // 记录结果
            int[] arr = new int[j-i];
            for (int k = i; k < j; k++) {
                arr[k-i] = k;
            }
            res.add(arr);
            // 左边界向右移动,找i+1开头的组合
            sum -= i;
            i++;
        }
    }

    return res.toArray(new int[res.size()][]);
}

45、扑克牌顺子

解析非常好

set+遍历

class Solution {
    public boolean isStraight(int[] nums) {
        Set<Integer> repeat = new HashSet<>();
        int max = 0, min = 14;
        for(int num : nums) {
            if(num == 0) continue; // 跳过大小王
            max = Math.max(max, num); // 最大牌
            min = Math.min(min, num); // 最小牌
            if(repeat.contains(num)) return false; // 若有重复,提前返回 false
            repeat.add(num); // 添加此牌至 Set
        }
        return max - min < 5; // 最大牌 - 最小牌 < 5 则可构成顺子
    }
}

排序+遍历

class Solution {
    public boolean isStraight(int[] nums) {
        int joker = 0;
        Arrays.sort(nums); // 数组排序
        for(int i = 0; i < 4; i++) {
            if(nums[i] == 0) joker++; // 统计大小王数量
            else if(nums[i] == nums[i + 1]) return false; // 若有重复,提前返回 false
        }
        return nums[4] - nums[joker] < 5; // 最大牌 - 最小牌 < 5 则可构成顺子
    }
}

46、圆圈中最后剩下的数

题解很清晰,约瑟夫环的问题

最后的幸存者在数组中的下标一定是0

class Solution {
    public int lastRemaining(int n, int m) {
        // 最后的幸存者在数组中的下标一定是0
        int survivorIndex = 0;
        // 从最后一个人往前推,2个人时,幸存者的下标;3个人时,幸存者的下标……
        for (int personLeft = 2; personLeft <= n; personLeft++) {
            survivorIndex = (survivorIndex + m) % personLeft;
        }
        return survivorIndex;
    }
}

63、数据流中的中位数

优先队列——堆

小顶堆保存的是大的一半,堆顶是最小的

大顶堆保存的是小的一半,堆顶是最大的

如果两边目前数量一样,不确定是大的一半还是小的一半,如果是小的一半,放入大顶堆中,会把最大的顶出来。放入小顶堆,不一定可以顶出最小值

class MedianFinder {
    Queue<Integer> A, B;//构建大小顶堆
    public MedianFinder() {
        A = new PriorityQueue<>(); // 小顶堆,保存较大的一半
        B = new PriorityQueue<>((x, y) -> (y - x)); // 大顶堆,保存较小的一半
    }
    public void addNum(int num) {
        if(A.size() != B.size()) {//当两边不相等的时候,即 N为 奇数
            A.add(num);//先往A里加,肯定是大的留下来,小的在堆顶
            B.add(A.poll());//B里保存A中最小的
        } else {
            B.add(num);//当两边相等的时候,N为偶数,向B里加,最大的在堆顶
            A.add(B.poll());//A里保存B里最大的
        }
    }
    public double findMedian() {
        return A.size() != B.size() ? A.peek() : (A.peek() + B.peek()) / 2.0;
        //当 m = nm=n( NN 为 偶数):则中位数为 (( AA 的堆顶元素 + BB 的堆顶元素 )/2)/2。
        //当 m !=n( NN 为 奇数):则中位数为 AA 的堆顶元素
    }
}

64、滑动窗口的最大值

堆删除任一一个节点是O(n)的

单调队列
peekLast()方法用于返回此双端队列表示的队列的最后一个元素,但不删除该元素

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        //单调队列
        //下面是要注意的点:
        //队列按从大到小放入
        //如果首位值(即最大值)不在窗口区间,删除首位
        //如果新增的值小于队列尾部值,加到队列尾部
        //如果新增值大于队列尾部值,删除队列中比新增值小的值,如果在把新增值加入到队列中
        //如果新增值大于队列中所有值,删除所有,然后把新增值放到队列首位,保证队列一直是从大到小
        if (nums.length == 0)   return nums;

        Deque<Integer> deque = new LinkedList<>();
        int[] arr = new int[nums.length - k + 1];
        int index = 0;  //arr数组的下标

        //未形成窗口区间
        for (int i = 0; i < k; i++) {
            //队列不为空时,当前值与队列尾部值比较,如果大于,删除队列尾部
            while (!deque.isEmpty() && nums[i] > deque.peekLast())  deque.removeLast();
            //执行完上面的循环后,队列中要么为空,要么值都比当前值大,然后就把当前值添加到队列中
            deque.addLast(nums[i]);
        }
        //窗口区间刚形成后,把队列首位值添加到队列中
        //因为窗口形成后,就需要把队列首位添加到数组中,而下面的循环是直接跳过这一步的,所以需要我们直接添加
        arr[index++] = deque.peekFirst();

        //窗口区间形成
        for (int i = k; i < nums.length; i++) {
       
            //i-k表示上一个窗口的第一个元素,在当前区间没有,如果首位等于nums[i-k],判断max值时,需要先删除该元素,添加新的当前值
            //队列保持递减,所以队首元素deque[0]是保存了窗口的最大值,如果滑动了一下窗口,窗口中被删除的元素nums[i-1]是队首元素deque[0],说明窗口中的最大值被删掉了,就得把队列中的最大值deque[0]也一并删掉
            if (deque.peekFirst() == nums[i - k])   deque.removeFirst();
            //如果队列不为空,带添加值比最大值大,则删除队列中比当前值小的所有元素
            while (!deque.isEmpty() && nums[i] > deque.peekLast())  deque.removeLast();
            //把当前值添加到队列中
            deque.addLast(nums[i]);
            //把队列的首位值添加到arr数组中
            arr[index++] = deque.peekFirst();
        }
        return arr;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值