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动态规划来做,但是没看懂
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;
}
}