剑指offer [1- 50 ]-- java题解
- 刷题地址
- 1 数组中重复的数字
- 2 二维数组中的查找
- 3 替换空格
- 4 从尾到头打印链表
- 5 重建二叉树
- 6 二叉树的下一个结点
- 7 用两个栈实现队列
- 8 斐波那契数列
- 9 旋转数组的最小数字
- 10 矩阵中的路径
- 11 机器人的运动范围
- 12 剪绳子
- 13 二进制中1的个数
- 14 数值的整数次方
- 15 打印从1到最大的n位数
- 16 删除链表的节点
- 17 正则表达式匹配
- 18 表示数值的字符串
- 19 调整数组顺序使奇数位于偶数前面(一)
- 20 链表中倒数最后k个结点
- 21 链表中环的入口结点
- 22 反转链表
- 23 合并两个排序的链表
- 24 树的子结构
- 25 二叉树的镜像
- 26 对称的二叉树
- 27 顺时针打印矩阵
- 28 包含min函数的栈
- 29 栈的压入、弹出序列
- 30 从上往下打印二叉树
- 31 二叉搜索树的后序遍历序列
- 32 二叉树中和为某一值的路径(二)
- 33 复杂链表的复制
- 34 二叉搜索树与双向链表
- 35 序列化二叉树
- 36 字符串的排列
- 37 数组中出现次数超过一半的数字
- 38 最小的K个数
- 39 数据流中的中位数
- 40 连续子数组的最大和
- 41 整数中1出现的次数(从1到n整数中1出现的次数)
- 42 数字序列中某一位的数字
- 43 把数组排成最小的数
- 44 把数字翻译成字符串
- 45 礼物的最大价值
- 46 最长不含重复字符的子字符串
- 47 丑数
- 48 第一个只出现一次的字符
- 49 数组中的逆序对
- 50 两个链表的第一个公共结点
刷题地址
https://www.nowcoder.com/exam/oj/ta?tpId=13
1 数组中重复的数字
在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组[2,3,1,0,2,5,3],那么对应的输出是2或者3。存在不合法的输入的话输出-1
时间复杂度:O(n)
空间复杂度:O(1)
解析
既然数组长度为n只包含了0到n−1的数字,那么如果数字没有重复,这些数字排序后将会与其下标一一对应。那我们就可以考虑遍历数组,每次检查数字与下标是不是一致的,一致的说明它在属于它的位置上,不一致我们就将其交换到该数字作为下标的位置上,如果交换过程中,那个位置已经出现了等于它下标的数字,那肯定就重复了。
代码
public class Solution {
public int duplicate (int[] numbers) {
// write code here
HashSet<Integer> set = new HashSet<>();
for(int i = 0; i < numbers.length; i++){
if(!set.add(numbers[i])){
return numbers[i];
}
}
return -1;
}
}
2 二维数组中的查找
在一个二维数组array中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
[
[1,2,8,9],
[2,4,9,12],
[4,7,10,13],
[6,8,11,15]
]
给定 target = 7,返回 true。
给定 target = 3,返回 false。
解析
首先看四个角,左上与右下必定为最小值与最大值,而左下与右上就有规律了:左下元素大于它上方的元素,小于它右方的元素,右上元素与之相反。既然左下角元素有这么一种规律,相当于将要查找的部分分成了一个大区间和小区间,每次与左下角元素比较,我们就知道目标值应该在哪部分中,于是可以利用分治思维来做。
具体做法:
- step 1:首先获取矩阵的两个边长,判断特殊情况。
- step 2:首先以左下角为起点,若是它小于目标元素,则往右移动去找大的,若是他大于目标元素,则往上移动去找小的。
- step 3:若是移动到了矩阵边界也没找到,说明矩阵中不存在目标值。
代码
public class Solution {
public boolean Find(int target, int [][] array) {
int r = array.length - 1;
int c = 0;
while(r >= 0 && c < array[0].length){
int cmp = array[r][c];
if(cmp == target) return true;
else if(cmp > target) r--;
else c++;
}
return false;
}
}
3 替换空格
请实现一个函数,将一个字符串s中的每个空格替换成“%20”。
例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
代码
public class Solution {
public String replaceSpace (String s) {
StringBuilder sb = new StringBuilder();
char[] cs = s.toCharArray();
for(char c : cs){
if(c == ' '){
sb.append("%20");
}else{
sb.append(c);
}
}
return sb.toString();
}
}
4 从尾到头打印链表
输入一个链表的头节点,按链表从尾到头的顺序返回每个节点的值(用数组返回)。
解析
用栈实现,因为栈是先进后出的,符合逆序的特点
代码
public class Solution {
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
ArrayList<Integer> res = new ArrayList<>();
LinkedList<Integer> stack = new LinkedList<>();//栈
while(listNode != null){
stack.push(listNode.val);
listNode = listNode.next;
}
while(!stack.isEmpty()){
res.add(stack.pop());
}
return res;
}
}
5 重建二叉树
给定节点数为 n 的二叉树的前序遍历和中序遍历结果,请重建出该二叉树并返回它的头结点。
例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建出如下图所示。
解析
二叉树的根节点就是前序序列的第一个节点,这样就可以把中序序列拆分成两部分,根节点前面就是左子树,后面就是右子树,那么就知道了左右子树的节点数量,依此就可以对前序序列也进行拆分,这样左右子树的前序、中序序列都知道了,递归构建左右子树就可以了。
代码
public class Solution {
HashMap<Integer, Integer> map = new HashMap<>();
public TreeNode reConstructBinaryTree(int [] pre,int [] vin) {
for (int i = 0; i < vin.length; i++) {
map.put(vin[i], i);
}
TreeNode node = dfs(pre, 0, pre.length - 1, vin, 0, vin.length - 1);
return node;
}
private TreeNode dfs(int[] pre, int pl, int pr, int[] vin, int vl, int vr) {
if (pl > pr || vl > vr) return null;
int k = map.get(pre[pl]) - vl;
TreeNode treeNode = new TreeNode(pre[pl]);
treeNode.left = dfs(pre, pl + 1, pl + k, vin, vl, vl + k - 1);
treeNode.right = dfs(pre, pl + 1 + k, pr, vin, vl + k + 1, vr);
return treeNode;
}
}
6 二叉树的下一个结点
给定一个二叉树其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的next指针。下图为一棵有9个节点的二叉树。树中从父节点指向子节点的指针用实线表示,从子节点指向父节点的用虚线表示
代码
import java.util.*;
public class Solution {
ArrayList<TreeLinkNode> list = new ArrayList<>();
public TreeLinkNode GetNext(TreeLinkNode pNode) {
if (pNode == null) return null;
TreeLinkNode cur = pNode;
//找到根节点
while (pNode.next != null) {
pNode = pNode.next;
}
//中序遍历,添加node
Inorder(pNode);
//返回下一个节点
for (int i = 0; i < list.size() - 1; i++) {
TreeLinkNode temp = list.get(i);
if(cur == temp){
return list.get(i + 1);
}
}
return null;
}
private void Inorder(TreeLinkNode node) {
if (node == null) return;
Inorder(node.left);
list.add(node);
Inorder(node.right);
}
}
二叉搜索树的第k个节点
public class Solution {
int count = 0;
TreeNode node = null;
public int KthNode (TreeNode proot, int k) {
// write code here
dfs(proot, k);
return node != null ? node.val : -1;
}
private void dfs(TreeNode root, int k){
if(root == null || count > k) return;
dfs(root.left, k);
count++;
if(count == k){
node = root;
return;
}
dfs(root.right, k);
}
}
7 用两个栈实现队列
用两个栈来实现一个队列,使用n个元素来完成 n 次在队列尾部插入整数(push)和n次在队列头部删除整数(pop)的功能。 队列中的元素为int类型。保证操作合法,即保证pop操作时队列内已有元素。
解析
push栈和pop栈来完成
代码
import java.util.Stack;
public class Solution {
Stack<Integer> stack1 = new Stack<Integer>();
Stack<Integer> stack2 = new Stack<Integer>();
public void push(int node) {
stack1.push(node);
pushTopop();
}
public int pop() {
pushTopop();
return stack2.pop();
}
private void pushTopop(){
if(stack2.isEmpty()){
while(!stack1.isEmpty()){
stack2.push(stack1.pop());
}
}
}
}
8 斐波那契数列
解析
动态规划
代码
public class Solution {
public int Fibonacci(int n) {
int[] dp = new int[n + 1];
if(n <= 2) return 1;
dp[0] = 0;
dp[1] = 1;
for(int i = 2; i < n + 1; i++){
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
}
9 旋转数组的最小数字
有一个长度为 n 的非降序数组,比如[1,2,3,4,5],将它进行旋转,即把一个数组最开始的若干个元素搬到数组的末尾,变成一个旋转数组,比如变成了[3,4,5,1,2],或者[4,5,1,2,3]这样的。请问,给定这样一个旋转数组,求数组中的最小值。
解析
具体做法:
- step 1:双指针指向旋转后数组的首尾,作为区间端点。
- step 2:若是区间中点值大于区间右界值,则最小的数字一定在中点右边。
- step 3:若是区间中点值等于区间右界值,则是不容易分辨最小数字在哪半个区间,比如[1,1,1,0,1],应该逐个缩减右界。
- step 4:若是区间中点值小于区间右界值,则最小的数字一定在中点左边。
- step 5:通过调整区间最后即可锁定最小值所在。
代码
易错
缩小右界 只能与右边界进行比较。
public class Solution {
public int minNumberInRotateArray(int [] array) {
int l = 0, r = array.length - 1;
while(l < r){
int mid = l + (r - l) / 2;
if(array[mid] > array[r]){
l = mid + 1;
}else if(array[mid] < array[r]){
r = mid;
}else{
r--;
}
}
return array[l];
}
}
寻找峰值
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f3KyrWWK-1679312763455)(${img}/image-20230318215851854.png)]
public class Solution {
public int findPeakElement (int[] nums) {
// write code here
int left = 0, right = nums.length - 1;
while(left < right){
int mid = (left + right) >> 1;
if(nums[mid] > nums[mid + 1]){
right = mid;
}
else{
left = mid + 1;
}
}
return left;
}
}
10 矩阵中的路径
请设计一个函数,用来判断在一个n乘m的矩阵中是否存在一条包含某长度为len的字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。
输入:
[[a,b,c,e],[s,f,c,s],[a,d,e,e]],“abcced”
返回值:
true
解析
枚举矩阵的每个位置作为路径的起点,只要能找到对应字符串就直接返回 true。
具体在每次搜索中,可以依次尝试相邻未访问格子的字母,只要能和单词对应位置匹配,就继续向下搜索。
代码
public class Solution {
int r = 0;
int c = 0;
public boolean hasPath(char[][] matrix, String word) {
// write code here
r = matrix.length;
c = matrix[0].length;
for (int i = 0; i < r; i++) {
for (int j = 0; j < c; j++) {
if (dfs(matrix, i, j, word, 0)) {
return true;
}
}
}
return false;
}
private boolean dfs(char[][] matrix, int i, int j, String word, int index) {
if (index == word.length()) return true;
if (i < 0 || i >= r || j < 0 || j >= c || matrix[i][j] != word.charAt(index)) return false;
char temp = matrix[i][j];
matrix[i][j] = '.';
return dfs(matrix, i + 1, j, word, index + 1) || dfs(matrix, i - 1, j, word, index + 1)
|| dfs(matrix, i, j + 1, word, index + 1)|| dfs(matrix, i, j - 1, word, index + 1) || (matrix[i][j] = temp) =='.';
}
}
11 机器人的运动范围
解析
地上有一个 rows 行和 cols 列的方格。坐标从 [0,0] 到 [rows-1,cols-1] 。一个机器人从坐标 [0,0] 的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于 threshold 的格子。 例如,当 threshold 为 18 时,机器人能够进入方格 [35,37] ,因为 3+5+3+7 = 18。但是,它不能进入方格 [35,38] ,因为 3+5+3+8 = 19 。请问该机器人能够达到多少个格子?
解析
和上题类似
代码
易错:添加isVisited
public class Solution {
boolean[][] isVisited;
int r;
int c;
int t;
public int movingCount(int threshold, int rows, int cols) {
isVisited = new boolean[rows][cols];
r = rows;
c = cols;
t = threshold;
return dfs(0, 0);
}
private int dfs(int i, int j){
if(i >= r || i < 0 || j >= c || j < 0 || isVisited[i][j] || cal(i) + cal(j) > t) return 0;
isVisited[i][j] = true;
return 1 + dfs(i + 1, j) + dfs(i - 1, j) + dfs(i, j + 1) + dfs(i, j - 1);
}
private int cal(int num){
int sum = 0;
while(num > 0){
sum += num % 10;
num /= 10;
}
return sum;
}
}
12 剪绳子
定义一个数组dp,其中dp[i]表示的是长度为i的绳子能得到的最大乘积。我们先把长度为i的绳子拆成两部分,一部分是j,另一部分是i-j,那么会有下面4种情况
1,j和i-j都不能再拆了
- dp[i]=j*(i-j);
2,j能拆,i-j不能拆
- dp[i]=dp[j]*(i-j);
3,j不能拆,i-j能拆
- dp[i]=j*dp[i-j];
4,j和i-j都能拆
- dp[i]=dp[j]*dp[i-j];
我们取上面4种情况的最大值即可。我们把它整理一下,得到递推公式如下
dp[i] = max(dp[i], (max(j, dp[j])) * (max(i - j, dp[i - j])));
public class Solution {
public int cutRope(int target) {
int[] dp = new int[target + 1];//dp[i]表示的是长度为i的绳子能得到的最大乘积
dp[1] = 1;
for (int i = 2; i <= target; i++) {
for (int j = 1; j < i; j++) {
dp[i] = Math.max(dp[i], (Math.max(j, dp[j])) * (Math.max(i - j, dp[i - j])));
}
}
return dp[target];
}
}
跳台阶扩展问题
一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶(n为正整数)总共有多少种跳法。
public class Solution {
public int jumpFloorII(int target) {
int[] dp = new int[target + 1];
//初始化前面两个
dp[0] = 1;
dp[1] = 1;
//依次乘2
for(int i = 2; i <= target; i++)
dp[i] = 2 * dp[i - 1];
return dp[target];
}
}
public class Solution {
public int jumpFloorII(int target) {
int[] dp = new int[target + 1];
dp[0] = 1;
dp[1] = 1;
for (int i = 2; i < target + 1; i++) {
for (int j = 0; j < i; j++) {
dp[i] += dp[j]; //dp[i] = dp[0] + dp[1] + .. +dp[i - 1]
}
}
return dp[target];
}
}
13 二进制中1的个数
public class Solution {
public int NumberOf1(int n) {
int res = 0;
while(n != 0){
n = n & (n - 1);//将最后一位从0变成1
res++;
}
return res;
}
}
求1+2+3+…+n
求1+2+3+…+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
public class Solution {
public int Sum_Solution(int n) {
boolean flag = (n > 0) && (n += Sum_Solution(n - 1)) > 0;
return n;
}
}
14 数值的整数次方
public class Solution {
public double Power(double base, int exponent) {
if (exponent < 0) {
base = 1 / base;
exponent = -exponent;
}
return pow(base, exponent);
}
//快速幂
private double pow(double base, int exponent) {
double res = 1;
while (exponent != 0) {
//可以再往上乘一个
if ((exponent & 1) != 0) {
res *= base;
}
//减少乘次数
exponent = exponent >> 1;
//叠加
base *= base;
}
return res;
}
}
15 打印从1到最大的n位数
输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数 999。
import java.util.*;
public class Solution {
public int[] printNumbers (int n) {
//找到该n+1位数的最小数字
int end = 1;
for(int i = 1; i <= n; i++)
end *= 10;
//从1遍历到n+1位数的最小数字输出
int[] res = new int[end - 1];
for(int i = 1; i < end; i++)
res[i - 1] = i;
return res;
}
}
16 删除链表的节点
给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。返回删除后的链表的头节点。
import java.util.*;
public class Solution {
public ListNode deleteNode (ListNode head, int val) {
//加入一个头节点
ListNode res = new ListNode(0);
res.next = head;
//前序节点
ListNode pre = res;
//当前节点
ListNode cur = head;
//遍历链表
while(cur != null){
//找到目标节点
if(cur.val == val){
//断开连接
pre.next = cur.next;
break;
}
pre = cur;
cur = cur.next;
}
//返回去掉头节点
return res.next;
}
}
删除链表中重复的结点
public class Solution {
public ListNode deleteDuplication(ListNode pHead) {
ListNode dummy = new ListNode(-1);
ListNode help = dummy;
while (pHead != null) {
// 进入循环时,确保了 pHead 不会与上一节点相同
if (pHead.next == null || pHead.next.val != pHead.val) {
help.next = pHead;
help = help.next;
}else{
// 如果 pHead 与下一节点相同,跳过相同节点(到达「连续相同一段」的最后一位)
while (pHead.next != null && pHead.val == pHead.next.val) pHead = pHead.next;
}
pHead = pHead.next;
}
help.next = null;
return dummy.next;
}
}
17 正则表达式匹配
易错
i从0开始,因为有a*这种存在
public class Solution {
public boolean match(String s, String p) {
//dp[i][j] 表示 s的前i个字符与 p中的前j个字符是否能够匹配
int n = s.length();
int m = p.length();
boolean[][] dp = new boolean[n + 1][m + 1];
dp[0][0] = true;
for(int i = 0; i < n + 1; i++){
for(int j = 1; j < m + 1; j++){
if(p.charAt(j - 1) != '*'){
dp[i][j] = i >= 1 && j >= 1 && dp[i - 1][j - 1] && isMath(s, p, i, j) ;
}else{
//* 匹配零个
boolean mathZero = j >= 2 && dp[i][j - 2];
//* 匹配多个
boolean matchMany = i >= 1 && j >= 1 && dp[i - 1][j] && isMath(s, p, i, j - 1);
dp[i][j] = mathZero || matchMany;
}
}
}
return dp[n][m];
}
private boolean isMath(String s, String p, int i, int j){
return s.charAt(i - 1) == p.charAt(j - 1) || p.charAt(j - 1) == '.';
}
}
18 表示数值的字符串
public boolean isNumeric(String str) {
int n = str.length();
int index = 0;
boolean hasNum = false, hasE = false;
boolean hasSign = false, hasDot = false;
while (index < n && str.charAt(index) == ' ') index++;
while (index < n) {
while (index < n && str.charAt(index) >= '0' && str.charAt(index) <= '9') {
index++;
hasNum = true;
}
if (index == n) {
break;
}
char c = str.charAt(index);
if (c == 'e' || c == 'E') {
if (hasE || !hasNum) {
return false;
}
hasE = true;
hasNum = false;
hasSign = false;
hasDot = false;
} else if (c == '+' || c == '-') {
if (hasSign || hasNum || hasDot) {
return false;
}
hasSign = true;
} else if (c == '.') {
if (hasDot || hasE) {
return false;
}
hasDot = true;
} else if (c == ' ') {
break;
} else {
return false;
}
index++;
}
while (index < n && str.charAt(index) == ' ') index++;
return hasNum && index == n;
}
19 调整数组顺序使奇数位于偶数前面(一)
输入一个长度为 n 整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前面部分,所有的偶数位于数组的后面部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
import java.util.*;
public class Solution {
public int[] reOrderArray (int[] array) {
int n = array.length;
int[] res = new int[n];
//统计奇数个数
int odd = 0;
//遍历统计
for(int i = 0; i < n; i++){
if(array[i] % 2 == 1)
odd++;
}
//x与y分别表示答案中奇偶数的坐标
int x = 0, y = odd;
for(int i = 0; i < n; i++){
//奇数在前
if(array[i] % 2 == 1){
res[x] = array[i];
x++;
//偶数在后
}else{
res[y] = array[i];
y++;
}
}
return res;
}
}
调整数组顺序使奇数位于偶数前面(二)
//除了上面的方法外还有
public int[] reOrderArrayTwo(int[] array) {
int l = 0, r = array.length - 1, tmp = 0;
// 终止条件:左右指针相遇
while (l < r) {
if ((array[l] & 1) == 1) {
// 奇数:左指针往右挪
l++;
} else {
// 偶数:交换后,右指针往左挪
tmp = array[l];
array[l] = array[r];
array[r] = tmp;
r--;
}
}
return array;
}
20 链表中倒数最后k个结点
public ListNode FindKthToTail (ListNode pHead, int k) {
// write code here
ListNode fast = pHead;
ListNode slow = pHead;
for (int i = 0; i < k; i++) {
if(fast == null) return null;
fast = fast.next;
}
while (fast != null) {
fast = fast.next;
slow = slow.next;
}
return slow;
}
21 链表中环的入口结点
public ListNode EntryNodeOfLoop(ListNode pHead) {
if(pHead == null) return null;
ListNode fast = pHead;
ListNode slow = pHead;
while(fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
if(slow == fast){
fast = pHead;
while(fast != slow){
fast = fast.next;
slow = slow.next;
}
return fast;
}
}
return null;
}
22 反转链表
public class Solution {
public ListNode ReverseList(ListNode head) {
ListNode next = null;
ListNode last = null;
while (head != null) {
next = head.next;
head.next = last;
last = head;
head = next;
}
return last;
}
}
23 合并两个排序的链表
public class Solution {
public ListNode Merge(ListNode list1, ListNode list2) {
if (list1 == null) return list2;
if (list2 == null) return list1;
ListNode help = new ListNode(Integer.MIN_VALUE);
ListNode cur = help;
while (list1 != null && list2 != null) {
if (list1.val <= list2.val) {
cur.next = list1;
list1 = list1.next;
} else {
cur.next = list2;
list2 = list2.next;
}
cur = cur.next;
}
cur.next = list1 == null ? list2 : list1;
return help.next;
}
}
24 树的子结构
public class Solution {
public boolean HasSubtree(TreeNode root1,TreeNode root2) {
if(root1 == null || root2 == null) return false;
if(judge(root1, root2)) return true; //当前节点是子树
//先序遍历
return HasSubtree(root1.left, root2) || HasSubtree(root1.right, root2);
}
private boolean judge(TreeNode root1, TreeNode root2) {
if(root2 == null) return true;//子树到头了
if(root1 == null) return false;//没遍历完子树
if(root1.val != root2.val) return false;
return judge(root1.left, root2.left) && judge(root1.right, root2.right);
}
}
25 二叉树的镜像
public TreeNode Mirror (TreeNode pRoot) {
// 先序遍历,从顶向下交换
if(pRoot == null) return null;
TreeNode temp = pRoot.left;
pRoot.left = pRoot.right;
pRoot.right = temp;
Mirror(pRoot.left);//左子树交换
Mirror(pRoot.right);//右子树交换
return pRoot;
}
26 对称的二叉树
public class Solution {
boolean isSymmetrical(TreeNode pRoot) {
if(pRoot == null) return true;
return dfs(pRoot.left, pRoot.right);
}
private boolean dfs(TreeNode left, TreeNode right) {
if(left == null && right == null) return true;
if(left == null || right == null || left.val != right.val) return false;
return dfs(left.left, right.right) && dfs(left.right, right.left);
}
}
27 顺时针打印矩阵
public ArrayList<Integer> printMatrix(int[][] matrix) {
ArrayList<Integer> res = new ArrayList<>();
int left = 0, right = matrix[0].length - 1, top = 0, down = matrix.length - 1;
while (left <= right && top <= down) {
for (int i = left; i <= right; i++) {
res.add(matrix[top][i]);
}
top++;
if (top > down) break;
for (int i = top; i <= down; i++) {
res.add(matrix[i][right]);
}
right--;
if (left > right) break;
for (int i = right; i >= left; i--) {
res.add(matrix[down][i]);
}
down--;
if (top > down) break;
for (int i = down; i >= top; i--) {
res.add(matrix[i][left]);
}
left++;
if (right < left) break;
}
return res;
}
28 包含min函数的栈
public class Solution {
Stack<Integer> stack1 = new Stack<>(); //用于栈的push 与 pop
Stack<Integer> stack2 = new Stack<>(); //min栈
public void push(int node) {
stack1.push(node);
if(stack2.isEmpty()||stack2.peek()>node){
stack2.push(node);
}else{
stack2.push(stack2.peek());//保持两个栈长度一样,方便pop
}
}
public void pop() {
stack1.pop();
stack2.pop();
}
public int top() {
return stack1.peek();
}
public int min() {
return stack2.peek();
}
}
29 栈的压入、弹出序列
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。
public class Solution {
public boolean IsPopOrder(int [] pushA,int [] popA) {
Stack<Integer> stack = new Stack<>();//辅助栈
for (int i = 0, j = 0; i < pushA.length; i++) {
stack.push(pushA[i]);//重新压栈
while (!stack.isEmpty() && stack.peek() == popA[j]) {//碰到自己就弹出,交给下面的
stack.pop();
j++;
}
}
return stack.isEmpty();
}
}
30 从上往下打印二叉树
public class Solution {
public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
ArrayList<Integer> res = new ArrayList<>();
if (root == null) return res;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
int n = queue.size();
while (n-- > 0) {
TreeNode node = queue.poll();
res.add(node.val);
if (node.left != null) queue.offer(node.left);
if (node.right != null) queue.offer(node.right);
}
}
return res;
}
}
31 二叉搜索树的后序遍历序列
class Solution {
public boolean verifyPostorder(int[] postorder) {
return recur(postorder, 0, postorder.length - 1);
}
boolean recur(int[] postorder, int i, int j) {
if(i >= j) return true;
int p = i;
while(postorder[p] < postorder[j]) p++;
int m = p;
while(postorder[p] > postorder[j]) p++;
return p == j && recur(postorder, i, m - 1) && recur(postorder, m, j - 1);
}
}
32 二叉树中和为某一值的路径(二)
题目中是给的到叶子结点
public class Solution {
ArrayList<ArrayList<Integer>> res = new ArrayList<>();
ArrayList<Integer> temp = new ArrayList<>();
public ArrayList<ArrayList<Integer>> FindPath(TreeNode root, int expectNumber) {
if(root == null) return res;
dfs(root, expectNumber);
return res;
}
private void dfs(TreeNode root, int expectNumber) {
if(root == null) return;
//先序遍历
expectNumber = expectNumber - root.val;
temp.add(root.val);
if(expectNumber == 0 && root.left == null && root.right == null){
res.add(new ArrayList<>(temp));
}
dfs(root.left, expectNumber);
dfs(root.right, expectNumber);
temp.remove(temp.size() - 1);
}
}
33 复杂链表的复制
class Solution {
public Node copyRandomList(Node head) {
if(head == null) return null;
Node cur = head;
Map<Node, Node> map = new HashMap<>();
// 3. 复制各节点,并建立 “原节点 -> 新节点” 的 Map 映射
while(cur != null) {
map.put(cur, new Node(cur.val));
cur = cur.next;
}
cur = head;
// 4. 构建新链表的 next 和 random 指向
while(cur != null) {
map.get(cur).next = map.get(cur.next);
map.get(cur).random = map.get(cur.random);
cur = cur.next;
}
// 5. 返回新链表的头节点
return map.get(head);
}
}
34 二叉搜索树与双向链表
public class Solution {
private TreeNode head = null;//起始位置
private TreeNode pre = null;//不断更新的位置
public TreeNode Convert(TreeNode pRootOfTree) {
if (pRootOfTree == null) return null;
//中序遍历即为有序
Convert(pRootOfTree.left);
if (head == null) {
head = pRootOfTree;//最左端
pre = head;
} else {
pre.right = pRootOfTree;//右指针指向后继
pRootOfTree.left = pre;//左指针指向前继
pre = pRootOfTree; //更新位置
}
Convert(pRootOfTree.right);
return head;
}
}
35 序列化二叉树
import java.util.*;
public class Solution {
StringBuilder sb = new StringBuilder();
String Serialize(TreeNode root) {
SerializeHelp(root, sb);
return sb.toString();
}
TreeNode Deserialize(String str) {
String[] values = str.split("_");
//队列中存放分隔后的数据,value和#
Queue<String> queue = new LinkedList<>();
for (int i = 0; i < values.length; i++) {
queue.offer(values[i]);
}
return DeserializeHelp(queue);
}
void SerializeHelp(TreeNode node, StringBuilder sb) {
//先序遍历 null 为#_ ,其他分隔符为_
if (node == null) {
sb.append("#_");
return; //结束
}
sb.append(node.val + "_");
SerializeHelp(node.left, sb);
SerializeHelp(node.right, sb);
}
TreeNode DeserializeHelp(Queue<String> queue) {
String value = queue.poll();
if (value.equals("#")) return null; //遇到# 表示空节点
TreeNode node = new TreeNode(Integer.valueOf(value));
node.left = DeserializeHelp(queue);
node.right = DeserializeHelp(queue);
return node;
}
}
36 字符串的排列
import java.util.*;
public class Solution {
ArrayList<String> res = new ArrayList<String>();
StringBuilder sb = new StringBuilder();
boolean[] used;
public ArrayList<String> Permutation(String str) {
if (str == null || str.length() == 0) return res;
char[] charStr = str.toCharArray();
//字典序
Arrays.sort(charStr);
//标记每个位置的字符是否被使用过
used = new boolean[str.length()];
recursion(charStr); //递归获取
return res;
}
public void recursion(char[] charStr) {
if (sb.length() == charStr.length) {
res.add(new String(sb));//临时字符串满了加入输出
return;
}
for (int i = 0; i < charStr.length; i++) {
if (used[i]) continue; //如果该元素已经被加入了则不需要再加入了
//当前的元素charStr[i]与同一层的前一个元素charStr[i-1]相同且charStr[i-1]没有用过(实际上已经用过了)
if (i > 0 && charStr[i - 1] == charStr[i] && !used[i - 1]) continue;
used[i] = true;
sb.append(charStr[i]);
recursion(charStr);
//回溯
used[i] = false;
sb.deleteCharAt(sb.length() - 1);
}
}
}
37 数组中出现次数超过一半的数字
public int MoreThanHalfNum_Solution(int[] array) {
int count = 0, res = 0;
for (int num : array) {
if (count == 0) res = num;
if (num != res) count--;
else count++;
}
return res;
}
38 最小的K个数
给定一个长度为 n 的可能有重复值的数组,找出其中不去重的最小的 k 个数。例如数组元素是4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4(任意顺序皆可)。
import java.util.ArrayList;
public class Solution {
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
quickSort(input, 0, input.length - 1, k); //最小的k个数字
ArrayList<Integer> res = new ArrayList<Integer>();
for(int i = 0; i < k; i++){
res.add(input[i]);
}
return res;
}
private void quickSort(int[] input, int l, int r, int k){
if(l >= r) return;
int random = l + (int)(Math.random() * (r - l + 1));
swap(input, random, r);
int p = partion(input, l, r);
if(p < k) quickSort(input, p + 1, r, k);//说明不够
else if(p > k) quickSort(input, l, p - 1, k);//超了
else return;
}
private int partion(int[] input, int l, int r){
int left = l - 1;
int cmp = input[r];
for(int i = l; i < r; i++){
if(input[i] <= cmp){
swap(input, ++left, i);
}
}
swap(input, left + 1, r);
return left + 1;
}
private void swap(int[] input, int a, int b){
int temp = input[a];
input[a] = input[b];
input[b] = temp;
}
}
39 数据流中的中位数
import java.util.*;
public class Solution {
PriorityQueue<Integer> min = new PriorityQueue<>();
PriorityQueue<Integer> max = new PriorityQueue<>((x, y) -> y - x);
public void Insert(Integer num) {
max.offer(num);
min.offer(max.poll());
if(max.size() < min.size()){
max.offer(min.poll());
}
}
public Double GetMedian() {
if(min.size() == max.size()){
return (min.peek() + max.peek()) / 2.0;
}else {
return max.peek() / 1.0;
}
}
}
40 连续子数组的最大和
public int FindGreatestSumOfSubArray(int[] array) {
int sum = 0;
int max = array[0];
for (int i = 0; i < array.length; i++) {
// 优化动态规划,确定sum的最大值
sum = Math.max(sum + array[i], array[i]);
// 每次比较,保存出现的最大值
max = Math.max(max, sum);
}
return max;
}
41 整数中1出现的次数(从1到n整数中1出现的次数)
输入一个整数 n ,求 1~n 这 n 个整数的十进制表示中 1 出现的次数
例如, 1~13 中包含 1 的数字有 1 、 10 、 11 、 12 、 13 因此共出现 6 次
注意:11 这种情况算两次
解析
对于整数n,将这个整数分为三部分:当前位数字cur,更高位数字high,更低位数字low,如:对于n=21034,当位数是十位时,cur=3,high=210,low=4。
我们从个位到最高位 依次计算每个位置出现1的次数:
在计算时,会出现三种情况
1)当前位的数字等于0时,例如n=21034,在百位上的数字cur=0,百位上是1的情况有:00100-00199,01100-01199,……,20100-20199。一共有21100种情况,即high*100
;
2)当前位的数字等于1时,例如n=21034,在千位上的数字cur=1,千位上是1的情况有:01000-01999,11000-11999,21000-21034。一共有21000+(34+1)种情况,即high*1000+(low+1)
。
3)当前位的数字大于1时,例如n=21034,在十位上的数字cur=3,十位上是1的情况有:00010-00019,……,21010-21019。一共有(210+1)*10
种情况,即(high+1)*10
。
public int NumberOf1Between1AndN_Solution(int n) {
int count = 0;
for (int i = 1; i <= n; i *= 10) { //i代表位数
int high = n / (i * 10); //更高位数字
int low = (n % i); //更低位数字
int cur = (n / i) % 10; //当前位数字
if (cur == 0) {
count += high * i;
} else if (cur == 1) {
count += high * i + (low + 1);
} else {
count += (high + 1) * i;
}
}
return count;
}
42 数字序列中某一位的数字
数字以 0123456789101112131415… 的格式作为一个字符序列,在这个序列中第 2 位(从下标 0 开始计算)是 2 ,第 10 位是 1 ,第 13 位是 1 ,以此类题,请你输出第 n 位对应的数字。
public int findNthDigit(int n) {
int dig = 1;//表示当前位数
long start = 1;//表示起始数字
long count = 9;//最大数量,结束条件
while (n > count) {
n -= count;//表示进入下一组
dig++;
start = start * 10;
count = 9 * dig * start;
}
//计算得到当前数字
String s = "" + (start + (n - 1) / dig);
//选择的下标
int idx = (n - 1) % dig;
return s.charAt(idx) - '0';
}
43 把数组排成最小的数
输入一个非负整数数组numbers,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。
例如输入数组[3,32,321],则打印出这三个数字能排成的最小数字为321323。
1.输出结果可能非常大,所以你需要返回一个字符串而不是整数
2.拼接起来的数字可能会有前导 0,最后结果不需要去掉前导 0
import java.util.*;
public class Solution {
public String PrintMinNumber(int[] nums) {
if (nums == null || nums.length == 0) return "";
String[] ss = new String[nums.length];
for (int i = 0; i < nums.length; i++) {
ss[i] = String.valueOf(nums[i]);
}
Arrays.sort(ss, (s1, s2) -> (s1 + s2).compareTo(s2 + s1));
StringBuilder sb = new StringBuilder();
for (String s : ss) {
sb.append(s);
}
return sb.toString();
}
}
44 把数字翻译成字符串
public class Solution {
public int solve (String nums) {
// write code here
int n = nums.length();
String s = " " + nums;
char[] cs = s.toCharArray();
//dp[i] 第i个位置有多少种译码方式
int[] dp = new int[n + 1];
dp[0] = 1;
for (int i = 1; i < n + 1; i++) {
// a : 代表「当前位置」单独形成 item
// b : 代表「当前位置」与「前一位置」共同形成 item
int a = cs[i] - '0', b = (cs[i - 1] - '0') * 10 + (cs[i] - '0');
// 如果 a 属于有效值,那么 dp[i] 可以由 dp[i - 1] 转移过来(一种结果),0没有映射
if (1 <= a && a <= 9) dp[i] = dp[i - 1];
// 如果 b 属于有效值,那么 dp[i] 可以由 dp[i - 2] 或者 dp[i - 1] & dp[i - 2] 转移过来(另一个结果)
if (10 <= b && b <= 26) dp[i] += dp[i - 2];
}
return dp[n];
}
}
45 礼物的最大价值
public int maxValue (int[][] grid) {
// write code here
int r = grid.length;
int c = grid[0].length;
int[][] dp = new int[r][c];
dp[0][0] = grid[0][0];
for (int i = 1; i < r; i++) {
dp[i][0] = grid[i][0] + dp[i - 1][0];
}
for (int i = 1; i < c; i++) {
dp[0][i] = grid[0][i] + dp[0][i - 1];
}
for (int i = 1; i < r; i++) {
for (int j = 1; j < c; j++) {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
}
}
return dp[r - 1][c - 1];
}
46 最长不含重复字符的子字符串
public int lengthOfLongestSubstring (String s) {
// write code here
if(s == null || s.length() == 0) return 1;
HashMap<Character, Integer> map = new HashMap<>();
char[] cs = s.toCharArray();
int res = 0;
for (int i = 0, j = 0; i < cs.length; i++) {
map.put(cs[i], map.getOrDefault(cs[i], 0) + 1);
while (map.get(cs[i]) > 1) {
map.put(cs[j], map.get(cs[j]) - 1);
j++;
}
if (i - j + 1 > res) {
res = i - j + 1;
}
}
return res;
}
47 丑数
把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第 n个丑数。
public class Solution {
public int GetUglyNumber_Solution(int index) {
if(index == 0) return 0;
int a = 0, b = 0, c = 0;//下标号
int[] dp = new int[index];
dp[0] = 1;
for (int i = 1; i < index; i++) {
//历史的丑数
// 第a丑数个数需要通过乘2来得到下个丑数,第b丑数个数需要通过乘2来得到下个丑数,同理第c个数
int na = dp[a] * 2, nb = dp[b] * 3, nc = dp[c] * 5;
dp[i] = Math.min(Math.min(na, nb), nc);
if(dp[i] == na) a++;
if(dp[i] == nb) b++;
if(dp[i] == nc) c++;
}
return dp[index - 1];
}
}
48 第一个只出现一次的字符
在一个长为字符串中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大小写).(从0开始计数)
import java.util.*;
public class Solution {
public int FirstNotRepeatingChar(String str) {
char[] chars = str.toCharArray();
HashMap<Character, Integer> map = new HashMap<>();
for (char c : chars) {
map.put(c, map.getOrDefault(c, 0) + 1);
}
for (int i = 0; i < str.length(); i++) {
if(map.get(str.charAt(i)) == 1){
return i;
}
}
return -1;
}
}
49 数组中的逆序对
public class Solution {
public int InversePairs(int [] array) {
//归并算法
//计数条件是左边大于右边
if (array == null || array.length < 2) return 0;
return (int)process(array, 0, array.length - 1) % 1000000007;
}
public long process(int [] array, int left, int right) {
if (left >= right) return 0;
int mid = (left + right) >> 1;
long leftnum = process(array, left, mid);//左边排好序
long rightnum = process(array, mid + 1, right);//右边排好序
long mergernum = merge(array, left, mid, right); //利用外排序将左右两边排好序
return leftnum + rightnum + mergernum;
}
public long merge(int [] array, int left, int mid, int right) {
int[] help = new int[right - left + 1];
int helpIndex = 0;//外排索引
int count = 0;//逆序对数量
int leftHead = left;//左边头索引
int rightHead = mid + 1;//右边头索引
while (leftHead <= mid && rightHead <= right) {
if (array[leftHead] <= array[rightHead]) {
help[helpIndex++] = array[leftHead++];
} else {
help[helpIndex++] = array[rightHead++];
count = count + mid - leftHead + 1;
}
}
while (leftHead <= mid) {
help[helpIndex++] = array[leftHead++];
}
while (rightHead <= right) {
help[helpIndex++] = array[rightHead++];
}
//覆盖回去
for (int i = 0; i < help.length; i++) {
array[left + i] = help[i];
}
return count % 1000000007;
}
}
50 两个链表的第一个公共结点
public class Solution {
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
ListNode p1 = pHead1;
ListNode p2 = pHead2;
while (p1 != p2) {
p1 = p1 != null ? p1.next : pHead2;
p2 = p2 != null ? p2.next : pHead1;
}
return p1;
}
}