《剑指 Offer I》刷题笔记 51_60

  • 位运算(简单)
    • 51. 二进制中 1 的个数
      • _解法1:逐伟判断
      • 解法2:巧用 n&(n-1)
    • 52. 不用加减乘除做加法(背题)
      • 解法1:位运算
  • 位运算(中等)
    • 53. 数组中数字出现的次数*
      • _解法1:Set(不满足题目要求)
      • 解法2:位运算:^ + 分组
    • 54. 数组中数字出现的次数 II
      • _解法1:map
      • 解法2:位运算(待续)
  • 数学(简单)
    • 55. 数组中出现超过一半的数字
      • _解法1:map
      • _解法2:巧妙解法
      • 解法3:摩尔投票法
    • 56. 构建乘积数组
      • 解法1:暴力
      • 解法2:技巧
  • 数学(中等)
    • 57. 剪绳子
      • 解法1:动态规划
    • 58. 和为 s 的连续正数序列
      • _解法1:暴力 二维 List -> 二维 int 数组
      • 解法2:滑动窗口
    • 59. 圆圈中最后剩下的数字
      • _解法1:暴力模拟
      • 解法2:数学解法(todo)
  • 模拟(中等)
    • 60. 顺时针打印矩阵
      • _解法1:暴力模拟
      • 解法2:设定遍界的模拟
    • 61. 栈的压入、弹出序列
      • 解法1:暴力模拟
      • 题解2:优化的模拟

小标题以 _ 开头的题目和解法代表独立想到思路及编码完成,其他是对题解的学习。

VsCode 搭建的 Java 环境中 sourcePath 配置比较麻烦,可以用 java main.java 运行(JDK 11 以后)

Go 的数据结构:LeetCode 支持 https://godoc.org/github.com/emirpasic/gods 第三方库。

go get github.com/emirpasic/gods

位运算(简单)

51. 二进制中 1 的个数

题目:剑指 Offer 15. 二进制中1的个数

_解法1:逐伟判断

注:Java 中无符号左移是 >>>,且 while 循环条件不能写成 wihle (n > 0)

public class Solution {public int hammingWeight(int n) {int res = 0;while (n != 0) {res += n & 1;n >>>= 1;}return res;}
}
func hammingWeight(num uint32) int {res := 0for num > 0 {if num&1 == 1 {res++}num >>= 1}return res
}

解法2:巧用 n&(n-1)

题解:面试题15. 二进制中 1 的个数(位运算,清晰图解)

n & (n - 1) 的作用:将 n 的二进制位最右边的 1 变成 0

思路:利用 n & (n - 1) 每次消去 n 最右边的 1,计算将 n 变成 0 经历的次数。

class Solution {public int hammingWeight(int n) {int res = 0;while (n != 0) {res++;n &= n - 1; // 消去n最右边的1}return res;}
}

52. 不用加减乘除做加法(背题)

题目:剑指 Offer 65. 不用加减乘除做加法

解法1:位运算

题解:面试题65. 不用加减乘除做加法(位运算,清晰图解)

⭐️ 题解:禁止套娃,如何用位运算完成加法?

^ 亦或:相当于 无进位 的 求和。

想象 10 进制下的模拟情况:19+1=20 无进位求和就是 10,而非 20

& 与:相当于求每位的进位数。

还是想象10进制下模拟情况:9+1=10,如果是用 & 的思路来处理,则 9+1 得到的进位数为 1,而不是 10,所以要用 <<1 向左再移动一位,就得到 10 了。

class Solution {/*** 递归*/public int add(int a, int b) {if (b == 0) return a;// 转换成 非进位和 + 进位和return add(a ^ b, (a & b) << 1);}/*** 迭代*/public int add0(int a, int b) {while (b != 0) {int tempSum = a ^ b; // 计算无进位的临时结果int carryNum = (a & b) << 1; // 计算进位结果        a = tempSum;b = carryNum;}return a;}/*** 脑筋急转弯*/public int add1(int a, int b) {return Math.addExact(a, b);}/*** 脑筋急转弯*/public int add2(int a, int b) {return Integer.sum(a, b);}
}

位运算(中等)

53. 数组中数字出现的次数*

题目:数组中数字出现的次数

_解法1:Set(不满足题目要求)

class Solution {public int[] singleNumbers(int[] nums) {Set<Integer> set = new HashSet<>();for (int num : nums) {if (set.contains(num))set.remove(num);elseset.add(num);}return set.stream().mapToInt(Integer::valueOf).toArray();}
}

解法2:位运算:^ + 分组

class Solution {public int[] singleNumbers(int[] nums) {int tmp = 0;// 求出两个只出现1次的数的 异或值for (int num : nums)tmp ^= num;// 保留最右的一个 1, 根据此进行分组// int group = tmp & (-tmp); // 简单写法int group = 1;while ((group & tmp) == 0)group <<= 1;// 分组计算int[] res = new int[2];for (int num : nums) {// 分组位为0的组if ((num & group) == 0)res[0] ^= num;// 分组位为1的组elseres[1] ^= num;}return res;}
}

注:以下两种写法等价,都是求出 tmp 最右边一位的 1,如 1001000110100010

int group = 1;
while ((group & tmp) == 0)group <<= 1;
int group = tmp & (-tmp);

54. 数组中数字出现的次数 II

题目:剑指 Offer 56 - II. 数组中数字出现的次数 II

_解法1:map

class Solution {public int singleNumber(int[] nums) {Map<Integer, Boolean> map = new HashMap<>();for (int num : nums)map.put(num, map.containsKey(num));for (Integer i : map.keySet())if (!map.get(i))return i;return -1;}
}

解法2:位运算(待续)

看不懂,哭了。

数学(简单)

55. 数组中出现超过一半的数字

题目:数组中出现次数超过一半的数字

_解法1:map

class Solution {public int majorityElement(int[] nums) {Map<Integer, Integer> map = new HashMap<>();for (int num : nums) {int count = map.getOrDefault(num, 0);map.put(num, count + 1);if (map.get(num) > nums.length / 2)return num;}return -1;}
}

_解法2:巧妙解法

思路:排序后直接取中间位置的数字。如果某个数字的出现次数大于一半,那么排序后中间数字必然是它。

class Solution {    public int majorityElement1(int[] nums) {Arrays.sort(nums);return nums[nums.length / 2];}
}

解法3:摩尔投票法

所谓摩尔投票法,核心就是对拼消耗

玩一个诸侯争霸的游戏,假设你方人口超过总人口一半以上,并且能保证每个人口出去干仗都能一对一同归于尽。最后还有人活下来的国家就是胜利。那就大混战呗,最差所有人都联合起来对付你(对应你每次选择作为计数器的数都是众数),或者其他国家也会相互攻击(会选择其他数作为计数器的数),但是只要你们不要内斗,最后肯定你赢。最后能剩下的必定是自己人。

class Solution {/*** 摩尔投票法* 混战中一换一, vote 为冠军队伍人数, 遇到自己人则 1, 遇到敌人则 -1* vote 为 0 则下一个来的人成为冠军团队*/public int majorityElement(int[] nums) {// vote 冠军队伍人数, selectNum 冠军队伍代表的数字int vote = 0, selectNum = 0;// 所有人轮流上台, 其中有 自己人 也有 敌人for (int num : nums) {// 冠军队伍人数为 0, 此时没有冠军队伍, 则下一个上台的人成为冠军队伍if (vote == 0) selectNum = num;// 判断是敌是友, 来决定增加人数还是减少人数vote += (num == selectNum) ? 1 : -1;}// 返回最终的冠军队伍return selectNum;}
}

56. 构建乘积数组

解法1:暴力

解法2:技巧

class Solution {/*** [1, 2, 3, 4, 5]* 左乘:[1, 1, 2, 6, 24]* 右乘:[120, 60, 20,  5, 1] (从右往左写的)* 最终结果:[120, 60, 40, 30, 24]*/public int[] constructArr(int[] a) {int[] res = new int[a.length];for (int i = 0, product = 1; i < a.length; i++) {res[i] = product; // 左乘(不包含自己)product *= a[i];}for (int i = a.length - 1, product = 1; i >= 0; i--) {res[i] *= product; // 右乘(不包含自己)product *= a[i];}return res;}
}

数学(中等)

57. 剪绳子

题目:剑指 Offer 14- I. 剪绳子

解法1:动态规划

class Solution {public int cuttingRope(int n) {// dp[i] 表示长度为 i 的绳子剪过程 m 段后长度的最大乘积(m>1)int[] dp = new int[n + 1];dp[2] = 1; // 初始化// 目标: 求 dp[n]for (int i = 3; i <= n; i++)// 首先对绳子剪长度为 j 的一段, j 范围是 2 <= j < i// 剪掉的长度为 1 的话, 对乘积没有任何增益, 所以从 2 开始剪for (int j = 2; j < i; j++) {// j * (i - j) 表示剪了长度 j 以后,剩下 (i-j) 不剪// j * dp[i - j] 表示剪了长度 j 以后, 剩下 (i-j) 继续剪, 从之前 dp 数组找最大值int nowBigger = Math.max(j * (i - j), j * dp[i - j]);// 对于同一个 i, 内层循环对不同的 j 拿到的 max 不同, 每次循环要更新 maxdp[i] = Math.max(dp[i], nowBigger);}return dp[n];}
}

58. 和为 s 的连续正数序列

题目:剑指 Offer 57 - II. 和为s的连续正数序列

_解法1:暴力 二维 List -> 二维 int 数组

有点滑动窗口的影子,可惜还是功夫不到家!

  • 始终应该把 滑动窗口的区域 当作一个 整体 对待,不能随便重置 right
  • 应当灵活的控制左右指针的移动来控制整个区间
  • 双重 List 转 int[][] 也不是个明智的做法。。
class Solution {public int[][] findContinuousSequence(int target) {int left = 1;List<List<Integer>> res = new ArrayList<>();while (left <= (target / 2)) {List<Integer> temp = new ArrayList<>();int sum = 0;int right = left;while (sum < target) {sum += right;  temp.add(right++);if (sum == target) res.add(temp);}left++;}// List<List<Integer>> ---> int[][]int [][] resArr = new int[res.size()][];for (int i = 0; i < res.size(); i++) {List<Integer> temp = res.get(i);int[] tempArr = new int[temp.size()];for (int j = 0; j < temp.size(); j++)tempArr[j] = temp.get(j);resArr[i] = tempArr;}return resArr;}
}

解法2:滑动窗口

题解:什么是滑动窗口,以及如何用滑动窗口解这道题

class Solution {public int[][] findContinuousSequence(int target) {int left = 1, right = 1, sum = 0;List<int[]> res = new ArrayList<>();while (left <= (target / 2)) {if (sum < target)// 右指针向右移动sum += right++;else if (sum > target)// 左指针向右移动sum -= left++;else {int[] arr = new int[right - left];for (int i = left; i < right; i++)arr[i - left] = i;res.add(arr);sum -= left;// 左边界向右移动left++;}}return res.toArray(new int[res.size()][]);}
}

59. 圆圈中最后剩下的数字

题目:剑指 Offer 62. 圆圈中最后剩下的数字

_解法1:暴力模拟

思路:完全按照题目意思进行编程模拟整个操作。。

  • Java 中使用 LinkedList 会超时,使用 ArrayList 可以通过(耗时高)
class Solution {public int lastRemaining(int n, int m) {List<Integer> list = new ArrayList<>();for (int i = 0; i < n; i++)list.add(i);int cur = 0;while (list.size() > 1) {cur += m - 1;if (cur >= list.size())cur = cur % list.size();list.remove(cur);}return list.get(0);}
}

解法2:数学解法(todo)

这题是有数学解法的,不过我看不懂,先放着。。。

模拟(中等)

60. 顺时针打印矩阵

题目:剑指 Offer 29. 顺时针打印矩阵

_解法1:暴力模拟

思路:走过就设置已访问过的标志,右下左上方向循环走。

class Solution {// 已访问标志public static final int FLAG = -952861280; public int[] spiralOrder(int[][] matrix) {// 边界情况if (matrix.length == 0 || matrix[0].length == 0)return new int[] {};// x - row, y - columnint x = matrix.length, y = matrix[0].length;// 当前访问的位置int curX = 0, curY = 0;// 存储结果的数组int[] res = new int[x * y];// 从 [0][0] 开始, 默认已访问res[0] = matrix[0][0];matrix[0][0] = FLAG;// idx - 结果数组的索引, direction - 0123 右下左上int idx = 0, direction = 0; while (true) {if (direction == 0) { // 右// 当前方向满足条件就一直走while (curY + 1 < y && matrix[curX][curY + 1] != FLAG) {curY++;res[++idx] = matrix[curX][curY];matrix[curX][curY] = FLAG;}// 下个方向满足条件才走, 否则直接结束if (curX + 1 < x && matrix[curX + 1][curY] != FLAG)direction = 1;else break;} else if (direction == 1) { // 下while (curX + 1 < x && matrix[curX + 1][curY] != FLAG) {curX++;res[++idx] = matrix[curX][curY];matrix[curX][curY] = FLAG;}if (curY - 1 >= 0 && matrix[curX][curY - 1] != FLAG)direction = 2;else break;} else if (direction == 2) { // 左while (curY - 1 >= 0 && matrix[curX][curY - 1] != FLAG) {curY--;res[++idx] = matrix[curX][curY];matrix[curX][curY] = FLAG;}if (curX - 1 >= 0 && matrix[curX - 1][curY] != FLAG)  direction = 3;else break;} else if (direction == 3) { // 上while (curX - 1 >= 0 && matrix[curX - 1][curY] != FLAG) {curX--;res[++idx] = matrix[curX][curY];matrix[curX][curY] = FLAG;}if (curY + 1 < y && matrix[curX][curY + 1] != FLAG) direction = 0;else break;}}return res;}
}

解法2:设定遍界的模拟

class Solution {public int[] spiralOrder(int[][] matrix) {if (matrix.length == 0) return new int[0];// 左右上下 边界int left = 0, right = matrix[0].length - 1, top = 0, bottom = matrix.length - 1;int idx = 0;int[] res = new int[(right + 1) * (bottom + 1)];while (true) {// 左 -> 右for (int i = left; i <= right; i++)res[idx++] = matrix[top][i];// 收缩上边界, 同时判断能否往下走if (++top > bottom) break;// 上 -> 下for (int i = top; i <= bottom; i++) res[idx++] = matrix[i][right];// 收缩右边界, 同时判断能否往左走if (--right < left) break;// 右 -> 左for (int i = right; i >= left; i--)res[idx++] = matrix[bottom][i];// 收缩下边界, 同时判断能否往上走if (--bottom < top) break;// 下 -> 上for (int i = bottom; i >= top; i--)res[idx++] = matrix[i][left];// 收缩左边界, 同时判断能否往右走if (++left > right) break;}return res;}
}

61. 栈的压入、弹出序列

题目:剑指 Offer 31. 栈的压入、弹出序列

解法1:暴力模拟

class Solution {public boolean validateStackSequences(int[] pushed, int[] popped) {Stack<Integer> stack = new Stack<>();int p1 = 0, p2 = 0, idx = 0;while (p2 < popped.length && idx < pushed.length + 1) {while (!stack.isEmpty() && stack.peek() == popped[p2]) {stack.pop();p2++;continue;}if (p1 < pushed.length)stack.push(pushed[p1++]);idx++;}return stack.isEmpty();}
}

题解2:优化的模拟

题解:面试题31. 栈的压入、弹出序列(模拟,清晰图解)

class Solution {public boolean validateStackSequences(int[] pushed, int[] popped) {Stack<Integer> stack = new Stack<>();int i = 0;for (int num : pushed) {stack.push(num);// 循环判断与出栈while (!stack.isEmpty() && stack.peek() == popped[i]) {stack.pop();i++;}}return stack.isEmpty();}
}

《剑指 Offer I》刷题笔记 51 ~ 61 题相关推荐

  1. 《剑指offer》刷题笔记(发散思维能力):求1+2+3+...+n

    <剑指offer>刷题笔记(发散思维能力):求1+2+3+-+n 转载请注明作者和出处:http://blog.csdn.net/u011475210 代码地址:https://githu ...

  2. 《剑指offer》刷题——【链表】从尾到头打印链表

    <剑指offer>刷题--[链表]-<从尾到头打印链表> 问题分析: 递归实现: 1. 无返回值 2. 有返回值(ArrayList) 问题分析: 从头到尾打印链表比较简单,那 ...

  3. 《剑指Offer》刷题之最小的K个数

    <剑指Offer>刷题之最小的K个数 我不知道将去向何方,但我已在路上! 时光匆匆,虽未曾谋面,却相遇于斯,实在是莫大的缘分,感谢您的到访 ! 题目: 给定一个数组,找出其中最小的K个数. ...

  4. 《剑指offer》刷题总结

    从三月初开始刷剑指offer上面的题,到现在花了近二十天的时间终于刷完了.应该说,掌握上面的技巧应付一些公司面试题和小公司的笔试题是完全没有问题的.之前参加一个公司笔试,算法题就有一题是剑指offer ...

  5. 【剑指Offer】个人学习笔记_38_字符串的排列

    目录 题目: [剑指 Offer 38. 字符串的排列](https://leetcode-cn.com/problems/zi-fu-chuan-de-pai-lie-lcof/) 题目分析 初始解 ...

  6. 【剑指Offer】个人学习笔记_41_数据流中的中位数

    目录 题目: [剑指 Offer 41. 数据流中的中位数](https://leetcode-cn.com/problems/shu-ju-liu-zhong-de-zhong-wei-shu-lc ...

  7. 【剑指Offer】个人学习笔记_15_二进制中1的个数

    目录 题目: [剑指 Offer 15. 二进制中1的个数](https://leetcode-cn.com/problems/er-jin-zhi-zhong-1de-ge-shu-lcof/) 题 ...

  8. 【剑指Offer】个人学习笔记_61_扑克牌中的顺子

    目录 题目: [剑指 Offer 61. 扑克牌中的顺子](https://leetcode-cn.com/problems/bu-ke-pai-zhong-de-shun-zi-lcof/) 题目分 ...

  9. 【剑指Offer】个人学习笔记_46_把数字翻译成字符串

    目录 题目: [剑指 Offer 46. 把数字翻译成字符串](https://leetcode-cn.com/problems/ba-shu-zi-fan-yi-cheng-zi-fu-chuan- ...

最新文章

  1. 工业4.0的十大关键词
  2. 压缩网络模型,或者是融合多个神经网络
  3. 使用ps命令输出进程列表--用Enki学Linux系列(17)
  4. C语言 | 关于e格式符的问题(附C例程)
  5. 关于zbar的libzbar.a不支持ipnone5的64bit问题
  6. Beta冲刺博客集合贴
  7. (数据库系统概论|王珊)第七章数据库设计-第四节:逻辑结构设计
  8. Js模块化开发的理解
  9. axios下载图片 node_vue+node.js手把手教你搭建一个直播平台(二)
  10. TeamCity+Rancher+Docker实现.Net Core项目DevOps(目前成本最小的DevOps实践)
  11. CentOS+Subversion
  12. 【智能制造】歌尔股份打造面向可重构和微服务的可穿戴产品智慧工厂
  13. 【日常训练赛】C - Prove Him Wrong
  14. P4556 [Vani有约会]雨天的尾巴(线段树合并)
  15. 经济基础知识(中级)【1】
  16. 微信公众号代运营的的技巧有哪些(2)
  17. 微信后台基于时间序的海量数据冷热分级架构设计实践
  18. MybatisPlus极速入门教程
  19. echarts折线图曲线,每个值上面添加小圆点或者小圆圈
  20. C# net6微服务架构之服务注册与发现工具Consul的下载与安装(for windows)

热门文章

  1. 为什么你总感觉情绪低落心情颓废?
  2. 什么是 Linux 中的显示管理器?
  3. 如何将namedtuples序列化为JSON
  4. 华为手机系统更新后有什么大的变化?
  5. 未来几十年替代手机的是什么产品?
  6. godotenv简介
  7. Java多线程基本概念
  8. 如何监视SQL Server索引的总大小
  9. azure 使用_如何使用Cloud Shell自动化Azure Active Directory(AAD)任务
  10. azure云服务器搭建连接_如何创建到Azure SQL数据库的链接服务器