最近在LeetCode刷了一些算法题总结了一下,顺序是按照剑指Offer进行的。下面是前30道
目录标题
剑指offer
04.二维数组中的查找
在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个高效的函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
[
[1, 4, 7, 11, 15],
[2, 5, 8, 12, 19],
[3, 6, 9, 16, 22],
[10, 13, 14, 17, 24],
[18, 21, 23, 26, 30]
]
给定 target = 5,返回 true。
给定 target = 20,返回 false。
public boolean findNumberIn2DArray(int[][] matrix, int target) {
for(int i = 0 ; i < matrix.length ; i ++){
for(int j = 0 ; j < matrix[0].length ; j ++){
if(matrix[i][j] == target){
return true;
}
}
}
return false;
}
05.替换空格
输入:s = “We are happy.”
输出:“We%20are%20happy.”
class Solution {
public String replaceSpace(String s) {
String ss="";
for (int i=0;i<s.length();i++){
if(s.charAt(i)==' '){
ss+="%20";
}else{
ss+=s.charAt(i);
}
}
return ss;
}
}
解法二:直接调用方法
s.replace(" ","%20")
解法三:与解法一相似
public String replaceSpace(String s) {
StringBuilder sb = new StringBuilder();
for(int i = 0 ; i < s.length(); i++){
char c = s.charAt(i);
if(c == ' ') sb.append("%20");
else sb.append(c);
}
return sb.toString();
}
06.从尾到头打印链表
输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。
输入:head = [1,3,2]
输出:[2,3,1]
public int[] reversePrint(ListNode head) {
Stack<Integer> stack=new Stack<Integer>();
ListNode p=head;
while (p!=null){
stack.push(p.val);
p=p.next;
}
int[] re=new int[stack.size()];
for (int i=0;i<re.length;i++){
re[i]=stack.pop();
}
return re;
}
07.重建二叉树
输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
例如,给出
前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树:
3
/ \
9 20
/ \
15 7
class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) { val = x; }
public static TreeNode buildTree(int[] preorder, int[] inorder) {
int len = preorder.length;
if(len == 0)
return null;
HashMap<Integer, Integer> indexMap = new HashMap<>(len);
for (int i = 0; i < inorder.length; i++) {
indexMap.put(inorder[i], i);
}
return buildHelper(0, 0, len-1, preorder, indexMap);
}
public static TreeNode buildHelper(int preleft, int inleft, int inright, int[] preorder, HashMap<Integer, Integer> indexMap) {
if( inleft > inright) {
return null;
}
int rootval = preorder[preleft];
int index = indexMap.get(rootval); //根节点在中序遍历数组中的位置
TreeNode root = new TreeNode(rootval);
root.left = buildHelper(preleft+1, inleft,index-1, preorder, indexMap);
root.right = buildHelper(preleft+index-inleft+1, index+1, inright, preorder, indexMap);
return root;
}
}
09.用两个栈实现队列
用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )
示例 1:
输入:
["CQueue","appendTail","deleteHead","deleteHead"]
[[],[3],[],[]]
输出:[null,null,3,-1]
示例 2:
输入:
["CQueue","deleteHead","appendTail","appendTail","deleteHead","deleteHead"]
[[],[],[5],[2],[],[]]
输出:[null,-1,null,null,5,2]
思路:
简单明了,带你直接看懂题目和例子。 输入: ["CQueue","appendTail","deleteHead","deleteHead"] 这里是要执行的方法,从左到右执行
[[],[3],[],[]]对应上面的方法,是上面方法的参数。CQueue和deleteHead方法不需要指定数字,只有添加才需要指定数字
1.创建队列,返回值为null
2.将3压入栈,返回值为null
3.将栈底的元素删除,也就是消息队列中先进来的元素,所以是deleteHead,返回该元素的数值,所以为3
4.继续删除栈底的元素,但是没有元素了,所以返回-1
所以就有了下面的输出 输出:[null,null,3,-1]
示例 2: 输入: ["CQueue","deleteHead","appendTail","appendTail","deleteHead","deleteHead"]
[[],[],[5],[2],[],[]]
1.创建队列,返回值为null
2.删除栈底的元素,但是没有元素,所以返回-1
3.把5压入栈,返回null
4.把2压入栈,返回null
5.删除栈底的一个元素,也就是消息队列中先进来的元素,所以是deleteHead,就是最先进来的5,返回值为5,
6.删除栈底的一个元素,就是后进来的2,返回值为2,
所以就有了下面的输出
输出:[null,-1,null,null,5,2]
有没有发现先进来的数字,首先显示出来了,但是题目中说要使用栈,栈是先进后出的,使用栈来实现先进先出,在这里使用两个栈就好了,从一个进来再到另一个栈,这样顺序就是先进先出了。题目的主旨写在第一句,就是,使用两个栈实现一个队列。
使用java的同学请注意,如果你使用Stack的方式来做这道题,会造成速度较慢; 原因的话是Stack继承了Vector接口,而Vector底层是一个Object[]数组,那么就要考虑空间扩容和移位的问题了。 可以使用LinkedList来做Stack的容器,因为LinkedList实现了Deque接口,所以Stack能做的事LinkedList都能做,其本身结构是个双向链表,扩容消耗少。 但是我的意思不是像100%代码那样直接使用一个LinkedList当做队列,那确实是快,但是不符题意
class CQueue {
LinkedList<Integer> stack1;
LinkedList<Integer> stack2;
public CQueue() {
stack1=new LinkedList<>();
stack2=new LinkedList<>();
}
public void appendTail(int value) {
stack1.add(value);
}
public int deleteHead() {
if(stack2.isEmpty()){
if(stack1.isEmpty()){
return -1;
}
while (!stack1.isEmpty()){
stack2.add(stack1.pop());
}
}else {
return stack2.pop();
}
return stack2.pop();
}
}
10.1斐波那契数列
写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项。斐波那契数列的定义如下:
F(0) = 0, F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
示例 1:
输入:n = 2
输出:1
示例 2:
输入:n = 5
输出:5
public int fib(int n) {
if (n <= 1) return n;
int first = 0;
int second = 1;
int result = 0;
while (--n > 0) {
result = first + second;
if (result >= 1000000007) {
result -= 1000000007;
}
first = second;
second = result;
}
return result;
}
10.2青蛙跳台阶问题
一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
示例 1:
输入:n = 2
输出:2
示例 2:
输入:n = 7
输出:21
示例 3:
输入:n = 0
输出:1
思路:就是斐波那契数列(fei’bo’na’qi)。f(n)=f(n-1)+f(n-2)。从前两个往后加方便理解
public int numWays(int n) {
int x=1;
int y=1;
int sum=0;
if(n<2){
return 1;
}
for(int i=2;i<=n;i++){
sum=x+y;
x=y;
sum=sum>=1000000007?(sum-1000000007):sum;//if语句,防止sum和大于指定范围
y=sum;
}
return sum;
}
11.旋转数组的最小数字
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。
示例 1:
输入:[3,4,5,1,2]
输出:1
示例 2:
输入:[2,2,2,0,1]
输出:0
思路:原数组是递增的,经过旋转以后会把一部分小数据转移到后面去,所以只要从第一位往后找,只要遇到一个不是递增的元素那就说明这个元素及其之后的元素都是旋转过去的,返回这个元素就行了(因为这个小集合肯定也是递增的)
public int minArray(int[] numbers) {
if(numbers.length==1){
return numbers[0];
}
int a=numbers[0];
for (int i=1;i<numbers.length;i++){
if(a>numbers[i]){
return numbers[i];
}
}
return a;
}
12.矩阵中的路径
请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一格开始,每一步可以在矩阵中向左、右、上、下移动一格。如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子。例如,在下面的3×4的矩阵中包含一条字符串“bfce”的路径(路径中的字母用加粗标出)。
[[“a”,“b”,“c”,“e”],
[“s”,“f”,“c”,“s”],
[“a”,“d”,“e”,“e”]]
但矩阵中不包含字符串“abfb”的路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入这个格子。
示例 1:
输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED"
输出:true
示例 2:
输入:board = [["a","b"],["c","d"]], word = "abcd"
输出:false
思路:dfs + 回溯;
使用二维布尔数组记录之前的位置是否已经被访问过,如果已经被访问过的话,则在 dfs 的过程中,直接 return false 即可。也就是说,此路已经不通了;
如果当前遍历到的字符不等于 board[i][j] 位置上的字符,那么说明此路也是不通的,因此返回 false;
至于递归结束的条件:如果指针 start 能够来到 word 的最后一个字符,那么说明能够在矩阵 board 中找到一条路径,此时返回 true;
在遍历到当前 board[i][j] 的时候,首先应将该位置的 visited[i][j] 设置为 true,表明访问过;
然后,递归地去 board[i][j] 的上下左右四个方向去找,剩下的路径;
在 dfs 的过程当中,如果某条路已经不通了,那么那么需要回溯,也就是将 visited[i][j] 位置的布尔值重新赋值为 fasle;
最后,返回 ans 即可。
public boolean exist(char[][] board, String word) {
if (board == null || board.length == 0 || board[0].length == 0) {
return false;
}
char[] chars = word.toCharArray();
boolean[][] visited = new boolean[board.length][board[0].length];
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[0].length; j++) {
// 从 (0, 0) 点开始进行 dfs 操作,不断地去找,
// 如果以 (0, 0) 点没有对应的路径的话,那么就从 (0, 1) 点开始去找
if (dfs(board, chars, visited, i, j, 0)) {
return true;
}
}
}
return false;
}
private boolean dfs(char[][] board, char[] chars, boolean[][] visited, int i, int j, int start) {
if (i < 0 || i >= board.length || j < 0 || j >= board[0].length
|| chars[start] != board[i][j] || visited[i][j]) {
return false;
}
if (start == chars.length - 1) {
return true;
}
visited[i][j] = true;
boolean ans = false;
ans = dfs(board, chars, visited, i + 1, j, start + 1)
|| dfs(board, chars, visited, i - 1, j, start + 1)
|| dfs(board, chars, visited, i, j + 1, start + 1)
|| dfs(board, chars, visited, i, j - 1, start + 1);
visited[i][j] = false;
return ans;
}
13.机器人的运动范围
地上有一个m行n列的方格,从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0] 的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),也不能进入行坐标和列坐标的数位之和大于k的格子。例如,当k为18时,机器人能够进入方格 [35, 37] ,因为3+5+3+7=18。但它不能进入方格 [35, 38],因为3+5+3+8=19。请问该机器人能够到达多少个格子?
示例 1:
输入:m = 2, n = 3, k = 1
输出:3
示例 2:
输入:m = 3, n = 1, k = 0
输出:1
思路:使用递归。递归太重要了。
class Solution {
boolean[][] visited;
public int movingCount(int m, int n, int k) {
visited = new boolean[m][n];
return dfs(0, 0, m, n, k);
}
private int dfs(int x, int y, int m, int n, int k) {
if (x >= m || y >= n || visited[x][y]
|| (x % 10 + x / 10 + y % 10 + y / 10) > k) {
return 0;
}
visited[x][y] = true;//防止机器人二次跳入
return 1 + dfs(x + 1, y, m, n, k) + dfs(x, y + 1, m, n, k);
}
}
容易出错地方,并非一一遍历。因为机器人可能被堵住,可能走投无路,但后面还有满足条件的。
下面是错误解法:
class Solution {
public int movingCount(int m, int n, int k) {
int ans = 0;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (k - (i / 10 + i % 10) - (j / 10 + j % 10) >= 0)
ans++;
}
}
return ans;
}
}
14.剪绳子
给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]…k[m-1] 。请问 k[0]k[1]…*k[m-1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
示例 1:
输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1
示例 2:
输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36
思路:数论
任何大于1的数都可由2和3相加组成(根据奇偶证明)
因为2*2=1*4,2*3>1*5, 所以将数字拆成2和3,能得到的积最大
因为2*2*2<3*3, 所以3越多积越大 时间复杂度O(n/3),用幂函数可以达到O(log(n/3)), 因为n不大,所以提升意义不大,我就没用。 空间复杂度常数复杂度O(1)
方法一
class Solution {
public int cuttingRope(int n) {
if (n<=3) return n-1;
int div = n/3;
int rem = n % 3;
long result = 1;
for (int i = 0; i < div; i++) {
result *= i<div-1 ? 3 : (rem == 2 ? 3*rem : (3+rem));
if (result >= 1000000007) {
result = result%1000000007;
}
}
return (int)result;
}
}
方法二
public int cuttingRope(int n) {
if (n==1 || n==2)
return 1;
if (n==3)
return 2;
int sum=1;
while (n>4){
sum*=3;
n-=3;
}
return sum*n;
}
15. 二进制中1的个数
请实现一个函数,输入一个整数(以二进制串形式),输出该数二进制表示中 1 的个数。例如,把 9 表示成二进制是 1001,有 2 位是 1。因此,如果输入 9,则该函数输出 2。
示例 1:
输入:00000000000000000000000000001011
输出:3
解释:输入的二进制串 00000000000000000000000000001011 中,共有三位为 '1'。
示例 2:
输入:00000000000000000000000010000000
输出:1
解释:输入的二进制串 00000000000000000000000010000000 中,共有一位为 '1'。
示例 3:
输入:11111111111111111111111111111101
输出:31
解释:输入的二进制串 11111111111111111111111111111101 中,共有 31 位为 '1'。
方法一:(n & 1) + (n >>> 1) (掌握)
若 (n & 1) = 1, 说明 n 的最低位是 1;真(一般1表示真,0表示假)
若 (n & 1) = 0, 说明 n 的最低位是 0。假
每次把 n 和 1 相与后,对 n 执行一次无符号右移运算(n >>> 1),统计 (n & 1) = 1 的次数,直到 n 为 0。
public class Solution {
// you need to treat n as an unsigned value
public int hammingWeight(int n) {
int count = 0;
while(n != 0){
if((n & 1) == 1)
count++;
n = n >>> 1;
}
return count;
}
}
方法二:
把输入的整数转换成二进制字符串,统计字符串中字符 ‘1’ 的个数。
public class Solution {
public int hammingWeight(int n) {
int count = 0;
String s = Integer.toBinaryString(n);
for(int i = 0; i < s.length(); i++){
if(s.charAt(i) == '1')
count++;
}
return count;
}
}
16. 数值的整数次方(不太懂移位方面的知识)
实现函数double Power(double base, int exponent),求base的exponent次方。不得使用库函数,同时不需要考虑大数问题。
示例 1:
输入: 2.00000, 10
输出: 1024.00000
示例 2:
输入: 2.10000, 3
输出: 9.26100
示例 3:
输入: 2.00000, -2
输出: 0.25000
解释: 2-2 = 1/22 = 1/4 = 0.25
public double myPow(double x, int n) {
//快速幂
//n可以用二进制表示
//9=1001=1*1 + 0*2 + 0*4 + 1*8
//x^9 = x^(1*1) x^(0*2) x^(0*4) x^(1*8)
if(x == 0) return 0;
//int范围[−2147483648,2147483647],当n = -2147483648,时执行 n = -n会越界,使用long类型
long b = n;
double res = 1.0;
if(b<0) {
b= -b;
x = 1/x;
}
while(b > 0) {
if((b&1) == 1) res *= x;//累乘
x *= x;//相当于x^2
b >>= 1;//右移一位,进行二分
}
return res;
}
17. 打印从1到最大的n位数
输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数 999。
示例 1:
输入: n = 1
输出: [1,2,3,4,5,6,7,8,9]
public int[] printNumbers(int n) {
int x=1;
for (int i = 0; i < n; i++) {
x*=10;
}
int []a=new int[x-1];
for (int i = 0; i < x-1; i++) {
a[i]=i+1;
}
return a;
}
18. 删除链表的节点
给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。
返回删除后的链表的头节点。
注意:此题对比原题有改动
示例 1:
输入: head = [4,5,1,9], val = 5
输出: [4,1,9]
解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.
示例 2:
输入: head = [4,5,1,9], val = 1
输出: [4,5,9]
解释: 给定你链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9.
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode deleteNode(ListNode head, int val) {
if(head==null){
return head;
}
ListNode cur=head;
ListNode pre=null;
if(cur.val==val){
return head.next;
}
while (cur.val!=val){
pre=cur;
cur=cur.next;
}
pre.next=pre.next.next;
return head;
}
}
19. 正则表达式匹配
请实现一个函数用来匹配包含’. ‘和’‘的正则表达式。模式中的字符’.‘表示任意一个字符,而’'表示它前面的字符可以出现任意次(含0次)。在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"abaca"匹配,但与"aa.a"和"ab*a"均不匹配。
示例 1:
输入:
s = "aa"
p = "a"
输出: false
解释: "a" 无法匹配 "aa" 整个字符串。
示例 2:
输入:
s = "aa"
p = "a*"
输出: true
解释: 因为 '*' 代表可以匹配零个或多个前面的那一个元素, 在这里前面的元素就是 'a'。因此,字符串 "aa" 可被视为 'a' 重复了一次。
示例 3:
输入:
s = "ab"
p = ".*"
输出: true
解释: ".*" 表示可匹配零个或多个('*')任意字符('.')。
示例 4:
输入:
s = "aab"
p = "c*a*b"
输出: true
解释: 因为 '*' 表示零个或多个,这里 'c' 为 0 个, 'a' 被重复一次。因此可以匹配字符串 "aab"。
示例 5:
输入:
s = "mississippi"
p = "mis*is*p*."
输出: false
不会、递归和角标有点绕
public boolean isMatch(String text, String pattern) {
char[] t = text.toCharArray();
char[] p = pattern.toCharArray();
return isMatchHelper(t, 0, p, 0);
}
private boolean isMatchHelper(char[] t, int index, char[] p, int pIndex) {
//匹配完成
if (index == t.length && pIndex == p.length) {
return true;
}
//未匹配完全
if (index != t.length && pIndex == p.length) {
return false;
}
//当前字符匹配判断
boolean matchSuc = index < t.length && (p[pIndex] == t[index] || p[pIndex] == '.');
if (p.length - pIndex >= 2 && p[pIndex + 1] == '*') {
//匹配0次或多次
return isMatchHelper(t, index, p, pIndex + 2) || (matchSuc && isMatchHelper(t, index + 1, p, pIndex));
}
return matchSuc && isMatchHelper(t, index + 1, p, pIndex + 1);
}
20. 表示数值的字符串
请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100"、“5e2”、"-123"、“3.1416”、"-1E-16"、“0123"都表示数值,但"12e”、“1a3.14”、“1.2.3”、"±5"及"12e+5.4"都不是。
思路:
编码没什么难度,难点在于归纳各种正确的情况
‘.’出现正确情况:只出现一次,且在e的前面
‘e’出现正确情况:只出现一次,且出现前有数字
‘+’‘-’出现正确情况:只能在开头和e后一位
class Solution {
public boolean isNumber(String s) {
if (s == null || s.length() == 0) return false;
//去掉首位空格
s = s.trim();
boolean numFlag = false;
boolean dotFlag = false;
boolean eFlag = false;
for (int i = 0; i < s.length(); i++) {
//判定为数字,则标记numFlag
if (s.charAt(i) >= '0' && s.charAt(i) <= '9') {
numFlag = true;
//判定为. 需要没出现过.并且没出现过e
} else if (s.charAt(i) == '.' && !dotFlag && !eFlag) {
dotFlag = true;
//判定为e,需要没出现过e,并且出过数字了
} else if ((s.charAt(i) == 'e' || s.charAt(i) == 'E') && !eFlag && numFlag) {
eFlag = true;
numFlag = false;//为了避免123e这种请求,出现e之后就标志为false
//判定为+-符号,只能出现在第一位或者紧接e后面
} else if ((s.charAt(i) == '+' || s.charAt(i) == '-') && (i == 0 || s.charAt(i - 1) == 'e' || s.charAt(i - 1) == 'E')) {
//其他情况,都是非法的
} else {
return false;
}
}
return numFlag;
}
}
21.调整数组顺序使奇数位于偶数前面
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。
示例:
输入:nums = [1,2,3,4]
输出:[1,3,2,4]
注:[3,1,2,4] 也是正确的答案之一。
一次快排
public static int[] exchange(int[] nums) {
int left = 0;
int right = nums.length - 1;
while (left < right) {
while (left < right && nums[left] % 2 != 0) {//从左边开始找到合适的(奇数)
left++;
}
while (left < right && nums[right] % 2 == 0) {//从右边开始找到合适的(偶数)
right--;
}
if (left < right) {//满足则交换
int temp = nums[left];
nums[left] = nums[right];
nums[right] = temp;
}
}
return nums;
}
22. 链表中倒数第k个节点
输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。例如,一个链表有6个节点,从头节点开始,它们的值依次是1、2、3、4、5、6。这个链表的倒数第3个节点是值为4的节点。
示例:
给定一个链表: 1->2->3->4->5, 和 k = 2.
返回链表 4->5.
思路: 思路非常简单 ,只需要定义快慢指针即可, 快指针比慢指针快 K–1 ,当快指针到达终点,满指针到达倒数第k个目标处
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode getKthFromEnd(ListNode head, int k) {
ListNode fast=head;
ListNode slow=head;
for(int i=0;i<k-1;i++){
fast=fast.next;
}
while(fast.next!=null){
slow=slow.next;
fast=fast.next;
}
return slow;
}
}
24. 反转链表
定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
思路:不懂(链表一大弱点)
https://blog.csdn.net/pwlwell/article/details/106386509
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
ListNode preNode = null;
while (head!= null) {
ListNode nextNode = head.next;
head.next = preNode;
preNode = head;
head= nextNode;
}
return preNode;
}
}
25. 合并两个排序的链表
输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。
示例1:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
思路:非递归
构建一个结点充当合并后的链表的头结点(这样方便最后返回整个链表),用cur表示合并后链表的当前结点,遍历两个链表,两个链表小的那个结点作为cur的后继。更新链表和cur的位置。最后当有一条链表为空退出循环时,将另一个链表的结点作为cur的后继,结束。
链表:时刻注意着节点(节点操作一切)
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode dummy=new ListNode(0);//申请一个伪头节点
ListNode cur=dummy;//cur用来指向新插入的节点
while(l1!=null&&l2!=null){
if(l1.val<=l2.val){//将值小的结点作为cur的后继
cur.next=l1;
l1=l1.next;//更新链表指针
}else {
cur.next=l2;
l2=l2.next;
}
cur=cur.next;
}
cur.next=l1!=null?l1:l2; // 若l1 == null,直接把l2后面插入进来。
return dummy.next;//dummy时空节点,新链表的头节点是dummy的后一个节点
}
26. 树的子结构
输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)
B是A的子结构, 即 A中有出现和B相同的结构和节点值。
例如:
给定的树 A:
3
/ \
4 5
/ \
1 2
给定的树 B:
4
/
1
返回 true,因为 B 与 A 的一个子树拥有相同的结构和节点值。
示例 1:
输入:A = [1,2,3], B = [3,1]
输出:false
示例 2:
输入:A = [3,4,5,1,2], B = [4,1]
输出:true
public boolean isSubStructure(TreeNode A, TreeNode B) {
if(A==null||B==null){
return false;
}
// 1. A树与B树完全相等
// 2. A的左子树与B树完全相等
// 3. A的右子树与B树完全相等
return dfs(A,B)||isSubStructure(A.left, B) || isSubStructure(A.right, B);
}
public boolean dfs(TreeNode A,TreeNode B){
if(B==null){
return true;
}
if(A==null){
return false;
}
// 1. A的根节点与B的根节点是否相同
// 2. A的左子树与B的左子树是否相同
// 3. A的右子树与B的右子树是否相同
return A.val==B.val&&dfs(A.left,B.left)&&dfs(A.right,B.right);
}
27. 二叉树的镜像
请完成一个函数,输入一个二叉树,该函数输出它的镜像。
例如输入:
4
/ \
2 7
/ \ / \
1 3 6 9
镜像输出:
4
/ \
7 2
/ \ / \
9 6 3 1
示例 1:
输入:root = [4,2,7,1,3,6,9]
输出:[4,7,2,9,6,3,1]
public TreeNode mirrorTree(TreeNode root) {
if (root!=null){
root = exchange(root);//交换左右节点
root.left = mirrorTree(root.left);//获得左子树的镜像
root.right = mirrorTree(root.right);//获得右子树的镜像
}
return root;
}
public TreeNode exchange(TreeNode root){//交换左右节点
if(root.left==null&&root.right==null) return root;
TreeNode tmp = root.left;
root.left = root.right;
root.right = tmp;
return root;
}
28. 对称的二叉树
请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。
例如,二叉树 [1,2,2,3,4,4,3] 是对称的。
1
/ \
2 2
/ \ / \
3 4 4 3
但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的:
1
/ \
2 2
\ \
3 3
示例 1:
输入:root = [1,2,2,3,4,4,3]
输出:true
示例 2:
输入:root = [1,2,2,null,3,null,3]
输出:false
public boolean isSymmetric(TreeNode root) {
if (root == null)
return true;
return helper(root.left, root.right);
}
public boolean helper(TreeNode root1, TreeNode root2) {
if (root1 == null && root2 == null)
return true;
if (root1 == null || root2 == null)
return false;
return root1.val == root2.val && helper(root1.left, root2.right) &&
helper(root1.right, root2.left);
}
29. 顺时针打印矩阵
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。
示例 1:
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,3,6,9,8,7,4,5]
示例 2:
输入:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
输出:[1,2,3,4,8,12,11,10,9,5,6,7]
思路;
强壮性判断必须要有,这里二维数组的强壮性判断是否是二维数组,如果不是那么返回int[0]
题解: 先明确顺时针到底是怎样规律 无非就是从左到右 从上到下 从右到左 从下到上的一个过程
确定起始位置:其实位置就是左上角,获取到它的坐标(这里的坐标不是普通意义上的坐标,只能说左上角到各个定点的位置,这样更贴切点)
条件:从左到右到头之后需要判断还有没有下一行,也就是压缩,下一行怎么表示呢,就是top+1,判断top+1是否大于bottom即可。如果大于就说明到头了如果没有那么就继续,以此类推
class Solution {
public int[] spiralOrder(int[][] matrix) {
if (matrix.length == 0) {
return new int[0];
}
int left = 0;
int right = matrix[0].length - 1;
int top = 0;
int bottom = matrix.length - 1;
int num = 0;
int[] arr = new int[(right + 1) * (bottom + 1)];
while (true) {
for (int i = left; i <= right; i++) {
arr[num] = matrix[top][i];
num++;
}
top++;
if (top > bottom) {
break;
}
for (int i = top; i <= bottom; i++) {
arr[num] = matrix[i][right];
num++;
}
right--;
if (left > right) {
break;
}
for (int i = right; i >= left; i--) {
arr[num] = matrix[bottom][i];
num++;
}
bottom--;
if (top > bottom) {
break;
}
for (int i = bottom; i >= top; i--) {
arr[num] = matrix[i][left];
num++;
}
left++;
if (left > right) {
break;
}
}
return arr;
}
}
30. 包含min函数的栈
定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,调用 min、push 及 pop 的时间复杂度都是 O(1)。
示例:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.min(); --> 返回 -3.
minStack.pop();
minStack.top(); --> 返回 0.
minStack.min(); --> 返回 -2.
思路:使用list假装成栈
class MinStack {
List<Integer> list;
public MinStack() {
list=new LinkedList<>();
}
public void push(int x) {
list.add(x);
}
public void pop() {
list.remove(list.size()-1);
}
public int top() {
return list.get(list.size()-1);
}
public int min() {
int min=Integer.MAX_VALUE;
for (int i:list){
min=Math.min(min,i);
}
return min;
}
}