题目:找出数组中重复的数字。
在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
示例 1:
输入:
[2, 3, 1, 0, 2, 5, 3]
输出:2 或 3
题解:
巧用HashSet的去重特点,遍历数据集合,向set中添加元素,如果添加返回false,则遇到重复的数据直接返回即可。
class Solution {
public int findRepeatNumber(int[] nums) {
HashSet set = new HashSet<Integer>();
for(int num: nums){
if(!set.add(num))
return num;
}
return -1;
}
}
题目:
在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个高效的函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
题解:
优化版暴力遍历法,根据条件“每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序”因此我们逐行遍历之前,判断该行的首位元素是否小于target目标值,末尾元素是否大于target目标值,两个条件同时满足时,遍历该行。
示例:
现有矩阵 matrix 如下:
[
[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。
class Solution {
public boolean findNumberIn2DArray(int[][] matrix, int target) {
int n = matrix.length;
if(n==0)
return false;
int m = matrix[0].length;
if(m==0)
return false;
for(int i=0; i<n;i++){
if(matrix[i][0]<=target && matrix[i][m-1]>=target){
for(int num: matrix[i]){
if(num==target)
return true;
}
}
}
return false;
}
}
题目:
输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。
示例 1:
输入:head = [1,3,2]
输出:[2,3,1]
题解:
使用栈的先进后出的特点,遍历链表放入栈中,再逐个出栈放入数组。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public int[] reversePrint(ListNode head) {
Stack<Integer> st = new Stack<Integer>();
int size = 0;
while(null!=head){
st.push(head.val);
size++;
head = head.next;
}
int[] arr = new int[size];
int j =0 ;
while(!st.isEmpty()){
arr[j] = st.pop();
j++;
}
return arr;
}
}
题目:
输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
例如,给出
前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树:
3
/ \
9 20
/ \
15 7
限制:
0 <= 节点个数 <= 5000
题解:
对于任意一颗树而言,
前序遍历的形式总是
[ 根节点, [左子树的前序遍历结果], [右子树的前序遍历结果] ]
中序遍历的形式总是
[ [左子树的中序遍历结果], 根节点, [右子树的中序遍历结果] ]
比如下面这棵树:
a
/ \
b c
/ \ / \
d e f g
根 左子树 右子树
前序遍历结果:a [b d e] [c f g]
左子树 根 右子树
中序遍历结果:[d b e] a [f c g]
思路:
1.利用中序数组获取当前节点左右子树的大小。
前序遍历的结果首元素就是这棵树的根节点a。
在中序数组中找到根节点a的位置,a左边的元素数量为左子树元素数量。
同理,a右边为右子树元素。
2.结合左右子树的大小 在前序数组中 定位左右子树的根节点。
在前序数组中:
左子树根节点b的索引 = a的索引+1;
右子树根节点c的索引 = a的索引+左子树的大小+1;
```
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public TreeNode buildTree(int[] preorder, int[] inorder) {
//key:中序数组的值 value:中序数组的索引
HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
for (int i = 0; i < preorder.length; i++) {
map.put(inorder[i],i);
}
return buildMyTree(preorder,inorder,map,0,preorder.length-1,0,preorder.length-1);
}
/**
* 参数解释
* @param pre_left 当前树在前序数组的左边界
* @param pre_right 当前树在前序数组的右边界
* @param in_left 当前树在中序数组的左边界
* @param in_right 当前树在中序数组的右边界
* @return
*/
public TreeNode buildMyTree(int[] preorder,int[] inorder,HashMap<Integer,Integer> map,
int pre_left,int pre_right,int in_left,int in_right){
//递归结束条件
if (pre_left>pre_right){
return null;
}
//当前树(包括为子树的情况)的根节点为前序数组的 pre_left
Integer pre_root = pre_left;
//当前树的根节点在中序数组中的索引
Integer in_root = map.get(preorder[pre_root]);
//创建当前树的根节点
TreeNode root = new TreeNode(preorder[pre_root]);
//左子树的大小
int leftSize = in_root-in_left;
//向左递归左子树
root.left = buildMyTree(preorder,inorder,map,pre_left+1,pre_left+leftSize,in_left, in_root-1);
//向右递归右子树
root.right = buildMyTree(preorder,inorder,map,pre_left+leftSize+1,pre_right,in_root+1,in_right);
return root;
}
}
题目:
用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 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]
题解:
该题比较简单,主要是利用栈的先进后出特点。两个栈”负负得正“就可达到先进先出的结果。需要注意的地方有:
1、连续增加的元素需要一次性的从stack1压入stack2;因此,添加flag记录连续增加的次数。
2、stack1向stack2压入元素时,需检查stack2是不是为空,只有stack2为空才允许压入新的元素。否则先进来的元素会被后来的元素挤到栈底。
class CQueue {
private final Stack<Integer> stack1;
private final Stack<Integer> stack2;
//用于记录stack1的添加次数
int flag = 0;
public CQueue() {
stack1 = new Stack<Integer>();
stack2 = new Stack<Integer>();
}
public void appendTail(int value) {
stack1.push(value);
flag++;
}
public int deleteHead() {
Integer pop;
//stack2只有为空时才允许再加入,否则先进来的元素会被压倒栈底
//当然flag>0表示有新元素已就位时才允许入stack2
if (stack2.size()==0 && flag>0){
for (int i = 0; i < flag; i++) {
stack2.push(stack1.pop());
}
//stack1的元素已全部压入stack2,次数清空
flag = 0;
}
try {
pop = stack2.pop();
return pop;
}catch (Exception e){
//pop的底层当stack为空时,抛出异常。
pop = -1;
return pop;
}
}
}
题目:
写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项(即 F(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
题解:
动态规划
以斐波那契数列性质 f(n + 1) = f(n) + f(n - 1)f(n+1)=f(n)+f(n−1) 为状态转移方程。
class Solution {
public int fib(int n) {
if (n==0)
return 0;
if (n==1)
return 1;
int result = 0;
int a = 0;
int b = 1;
for (int i = 2; i <= n; i++) {
result=(a+b)% 1000000007;
a = b;
b = result;
}
return result;
}
}
题目:
一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
示例 1:
输入:n = 2
输出:2
示例 2:
输入:n = 7
输出:21
示例 3:
输入:n = 0
输出:1
题解:
(图作者:jyd链接:https://leetcode-cn.com/problems/qing-wa-tiao-tai-jie-wen-ti-lcof/solution/mian-shi-ti-10-ii-qing-wa-tiao-tai-jie-wen-ti-dong/)
//暴力递归法
class Solution13 {
public int numWays(int n) {
if (n==0||n==1)
return 1;
return (numWays(n-1)+numWays(n-2))%1000000007;
}
}
//改进版递归,避免重复计算
class Solution12 {
private HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
public int numWays(int n) {
if (n==0||n==1)
return 1;
if (map.get(n)!=null)
return map.get(n);
map.put(n, (numWays(n-1)+numWays(n-2))%1000000007);
return map.get(n);
}
}
//迭代法
class Solution11 {
public int numWays(int n) {
if (n==0||n==1)
return 1;
HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
map.put(0,1);
map.put(1,1);
for (int i = 2; i <= n; i++) {
map.put(i, (map.get(i-1)+map.get(i-2))%1000000007);
}
return map.get(n);
}
}
题目:
请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一格开始,每一步可以在矩阵中向左、右、上、下移动一格。如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子。例如,在下面的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
题解:
(图作者:jyd链接:https://leetcode-cn.com/problems/ju-zhen-zhong-de-lu-jing-lcof/solution/mian-shi-ti-12-ju-zhen-zhong-de-lu-jing-shen-du-yo/)
class Solution {
public boolean exist(char[][] board, String word) {
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[0].length; j++) {
if (traceBack(board, word, i, j, 0))
return true;
}
}
return false;
}
public boolean traceBack(char[][] board, String word, int i, int j, int k) {
//递归结束条件
//1.碰壁则回溯
if (board.length <= i || i < 0 || board[0].length <= j || j < 0 || board[i][j] != word.charAt(k))
return false;
//2.word已经全部找完
if (k == word.length() - 1)
return true;
//1.不能走回头路
board[i][j] = '\0';
boolean res = traceBack(board, word, i + 1, j, k + 1) ||
traceBack(board, word, i - 1, j, k + 1) ||
traceBack(board, word, i, j + 1, k + 1) ||
traceBack(board, word, i, j - 1, k + 1);
//2.恢复原数组,便于后续递归回溯
board[i][j]=word.charAt(k);
return res;
}
}
题目:
地上有一个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
提示:
1 <= n,m <= 100
0 <= k <= 20
题解:
使用深度优先遍历、广度优先遍历(相关链接)。
深度优先遍历思路:
1. 深度优先搜索:可以理解为暴力法模拟机器人在矩阵中的所有路径。DFS 通过递归,先朝一个方向搜到底,再回溯至上个节点,沿另一个方向搜索,以此类推。
2. 剪枝: 在搜索中,遇到数位和超出目标值、此元素已访问,则应立即返回,称之为 可行性剪枝 。
3. 终止条件: 当 ① 行列索引越界 或 ② 数位和超出目标值 k 或 ③ 当前元素已访问过 时,返回 0 ,代表不计入可达解。
4. 递推工作:
记当前单元格 :将二维数组中索引 (i, j)位置记为1,代表此单元格已被访问过。
搜索下一单元格: 计算当前元素的 下、右 两个方向元素的数位和,并开启下层递归 。
6. 回溯返回值: 返回 1 + 右方搜索的可达解总数 + 下方搜索的可达解总数,代表从本单元格递归搜索的可达解总数。
class Solution {
public int movingCount(int m, int n, int k) {
//初始化一个二维数组,未经过的位置为0,已经过的设置为1
int[][] table = new int[m][n];
for (int i = 0; i < table.length; i++) {
for (int j = 0; j < table[0].length; j++) {
table[i][j] = 0;
}
}
return traceBack(table, 0, 0, k);
}
public static int traceBack(int[][] arr, int i, int j, int k) {
//3个终止条件
if (i >= arr.length || i < 0 || j >= arr[0].length || j < 0 || k < getMn(i, j) || arr[i][j] == 1)
return 0;
//经过的位置值为1,未经过的为0
arr[i][j] = 1;
//只会向下和向右移动,只有两个移动方向
return 1 + traceBack(arr, i + 1, j, k) +
traceBack(arr, i, j + 1, k);
}
//1 <= n,m <= 100
//0 <= k <= 20
//与k相关的约束条件计算
public static int getMn(int m, int n) {
return m % 10 + m / 10 % 10 + m / 100 + n % 10 + n / 10 % 10 + n / 100;
}
}
题目:
*给你一根长度为 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
提示:
2 <= n <= 58
//贪心算法
class Solution {
public static int cuttingRope(int n) {
if (n<=3)return n-1;
int res = 1;
while (n>4){
res*=3;
n-=3;
}
return res*n;
}
}
//动态规划
class Solution {
// 2<=m<=n n为绳子的长度,m为剪后的段数
public int cuttingRope(int n) {
int[] dp = new int[n+1];
//初始值
dp[2]=1;
//绳子长度为3到n的所有情况
for (int i = 3; i < n+1; i++) {
//将长度为i的绳子切后,最后一段长度的绳子为2到i的所有情况.
// 如果为1的话肯定不会取到最大面积,所以初始值为2.
for (int j = 2; j < i; j++) {
//第一层取最大值是针对最后一段j取不同值时的最大乘积
//第二层取最大值是指,(i-j)这段可能需要剪:dp[i-j]*j,也可能不需要:(i-j)*j
dp[i]=Math.max(dp[i], Math.max((i-j)*j,dp[i-j]*j));
}
}
return dp[n];
}
}
题目:
给你一根长度为 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。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
示例 1:
输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1
示例 2:
输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36
提示:
2 <= n <= 1000
class Solution {
public int cuttingRope(int n) {
if (n<=3)return n-1;
long res = 1L;
int p = (int) (1e9+7);
while (n>4){
res=res*3%p;
n-=3;
}
return (int) (res*n%p);
}
}
题目:
请实现一个函数,输入一个整数(以二进制串形式),输出该数二进制表示中 1 的个数。例如,把 9 表示成二进制是 1001,有 2 位是 1。因此,如果输入 9,则该函数输出 2。
示例 1:
输入:00000000000000000000000000001011
输出:3
解释:输入的二进制串 00000000000000000000000000001011 中,共有三位为 '1'。
示例 2:
输入:00000000000000000000000010000000
输出:1
解释:输入的二进制串 00000000000000000000000010000000 中,共有一位为 '1'。
示例 3:
输入:11111111111111111111111111111101
输出:31
解释:输入的二进制串 11111111111111111111111111111101 中,共有 31 位为 '1'。
提示:
输入必须是长度为 32 的 二进制串 。
题解:
位运算符的使用。
举例:目标值与逐位做 & 运算,非零个数就是目标值中1的个数。
10101
00001 & 00001 (非0)
10101
00010 & 00000 (0)
10101
00100 & 00100 (非0)
10101
01000 & 00000 (0)
10101
10000 & 10000 (非0)
public int hammingWeight(int n) {
int count=0;
for(int i=1;i<=32;i++)
if((n&(1<<(i-1)))!=0)
count++;
return count;
}
*题目:*实现 pow(x, n) ,即计算 x 的 n 次幂函数(即,xn)。不得使用库函数,同时不需要考虑大数问题。
示例 1:
输入:x = 2.00000, n = 10
输出:1024.00000
示例 2:
输入:x = 2.10000, n = 3
输出:9.26100
示例 3:
输入:x = 2.00000, n = -2
输出:0.25000
解释:2-2 = 1/22 = 1/4 = 0.25
class Solution {
public double myPow(double x, int n) {
if (x == 0) return 0;
long b = n;
double res = 1.0;
if (b<0){
x = 1/x;
b = -b;
}
while (b>0){
if ((b&1)==1) res*=x;
x*=x;
b>>=1;
}
return res;
}
}
题目:
输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数 999。
示例 1:
输入: n = 1
输出: [1,2,3,4,5,6,7,8,9]
说明:
用返回一个整数列表来代替打印
n 为正整数
题解:
当 n 较大时,end 会超出 int32 整型的取值范围,超出取值范围的数字无法正常存储。但由于本题要求返回 int 类型数组,相当于默认所有数字都在 int32 整型取值范围内,因此不考虑大数越界问题。
public int[] printNumbers(int n) {
int[] arr = new int[(int) Math.pow(10, n)-1];
for (int i = 1; i < Math.pow(10, n); i++) {
arr[i-1] = i;
}
return arr;
}
题目:
给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。
返回删除后的链表的头节点。
示例 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.
说明:
题目保证链表中节点的值互不相同
若使用 C 或 C++ 语言,你不需要 free 或 delete 被删除的节点
题解:
/**
* 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.val==val)
return head.next;
ListNode pre = head;
while (pre!=null && pre.next!=null){
if (pre.next.val==val){
pre.next = pre.next.next;
}
pre = pre.next;
}
return head;
}
}
示例 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
s 可能为空,且只包含从 a-z 的小写字母。
p 可能为空,且只包含从 a-z 的小写字母以及字符 . 和 *,无连续的 '*'。
题解:
动态规划。输入字符串是否匹配可以转化子字符串是否匹配。
传送门
class Solution {
public boolean isMatch(String s, String p) {
int m = s.length() +1;
int n = p.length() +1;
boolean[][] dp = new boolean[m ][n ];
dp[0][0] = true;
for (int i = 2; i < n; i+=2) {
dp[0][i] = dp[0][i-2] && p.charAt(i-1)=='*';
}
//s的前i个字符
//p的前j个字符
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
if (p.charAt(j-1)=='*'){
dp[i][j] = dp[i][j-2] || //1.
dp[i-1][j] && s.charAt(i-1)==p.charAt(j-2) || //2.
dp[i-1][j] && p.charAt(j-2)=='.'; //3.
}else {
dp[i][j] = dp[i-1][j-1] && s.charAt(i-1)==p.charAt(j-1) ||
dp[i-1][j-1] && p.charAt(j-1)=='.';
}
}
}
return dp[m-1][n-1];
}
}
题目:
请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100"、“5e2”、"-123"、“3.1416”、"-1E-16"、“0123"都表示数值,但"12e”、“1a3.14”、“1.2.3”、"±5"及"12e+5.4"都不是。
题解:
class Solution {
public boolean isNumber(String s) {
if (s == null || s.trim().length() == 0) return false;
s= s.trim();
try {
Double.valueOf(s);
} catch (Exception e) {
return false;
}
char c = s.charAt(s.length() - 1);
return (c >= '0' && c <= '9') || c == '.';
}
}
题目:
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。
示例:
输入:nums = [1,2,3,4]
输出:[1,3,2,4]
注:[3,1,2,4] 也是正确的答案之一。
提示:
0 <= nums.length <= 50000
1 <= nums[i] <= 10000
题解:
双指针法。
指针left从左向右移动,遇到偶数停止。
指针right从右向左移动,遇到奇数停止。
交换两个指针位置的元素。
继续移动两个指针,直到相遇。
class Solution {
public int[] exchange(int[] nums) {
int left = 0;
int right = nums.length-1;
int temp = 0;
while (left<right){
while (nums[left]%2==1 && left<right){
left++;
}
while (nums[right]%2==0 && left<right ){
right--;
}
temp = nums[left];
nums[left] = nums[right];
nums[right] = temp;
left++;
right--;
}
return nums;
}
}
题目:
输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。
例如,一个链表有 6 个节点,从头节点开始,它们的值依次是 1、2、3、4、5、6。这个链表的倒数第 3 个节点是值为 4 的节点。
示例:
给定一个链表: 1->2->3->4->5, 和 k = 2.
返回链表 4->5.
题解:
双指针法。
指针right先行。
走了k步后指针left开始遍历。两个指针速度一致,相距始终为k。
当right指针遍历完毕时,指针left到达倒数第k个节点。
class Solution {
public ListNode getKthFromEnd(ListNode head, int k) {
ListNode left = head;
ListNode right = head;
while (right!=null){
right = right.next;
k--;
if (k<0){
left = left.next;
}
}
return left;
}
}
题目:
定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
限制:
0 <= 节点个数 <= 5000
题解:
分别使用pre,cur,pos保存连续的三个节点。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
if (head==null||head.next==null)
return head;
ListNode pre = null;
ListNode cur = head;
ListNode pos = head.next;
while (cur.next!=null){
cur.next = pre;
pre = cur;
cur = pos;
pos = pos.next;
}
cur.next = pre;
return cur;
}
}
题目:
输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。
示例1:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
限制:
0 <= 链表长度 <= 1000
题解:
使用head记录新链表的入口。
pos1 、pos2分别指向待比较的两个节点。
cur记录比较后接入的节点。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if (l1 ==null)
return l2;
if(l2 ==null)
return l1;
ListNode head = null;
ListNode cur = new ListNode(0);
ListNode pos1 = l1;
ListNode pos2 = l2;
while (pos1!=null && pos2!=null){
if (pos1.val<=pos2.val){
cur.next = pos1;
cur = pos1;
pos1 = pos1.next;
}else {
cur.next = pos2;
cur = pos2;
pos2 = pos2.next;
}
if (head==null){
head = cur;
}
}
if (pos1==null){
cur.next = pos2;
}
if (pos2 ==null){
cur.next = pos1;
}
return head;
}
}
题目:
输入两棵二叉树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
限制:
0 <= 节点个数 <= 10000
题解:
思路:
若树B是树A的子结构,则子结构的根节点可能为树A的任意一个节点。因此,判断树B是树A的子结构,需要完成以下两步工作:
nA
;(对应函数 isSubStructure(A,B)
)nA
为根节点的子树是否包含树B。(对应函数:traceBack(A, B)
)解题步骤
isSubStructure(A,B)
函数
特例处理:当树A为空 或 树B为空时,直接返回false;
返回值:若树B是树A的子结构,则必须满足以下三种情况之一,因此用 || 连接。
①以节点A为根节点的子树包含树B,对应traceBack(A,B)
②树B是树A左子树的子结构,对应isSubStructure(A.left,B)
③树B是树A右子树的子结构,对应isSubStructure(A.right,B)
traceBack(A, B)
函数
终止条件:
①当节点B为空:B已完成匹配,返回true;
②当节点A为空:已经越过A的节点,匹配失败,返回false;
③当节点A和B的值不同:说明匹配失败,返回false;
返回值:
①判断A和B的左子节点是否相等,即traceBack(A.left,B.left)
;
②判断A和B的右子节点是否相等,即traceBack(A.right,B.right)
;
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public boolean isSubStructure(TreeNode A, TreeNode B) {
if (A==null || B==null)
return false;
if (A.val==B.val &&traceBack(A.left, B.left) && traceBack(A.right, B.right))
return true;
//1.递归寻找根节点,有一次递归结果为true则表示包含
return isSubStructure(A.left, B)||isSubStructure(A.right, B);
}
public Boolean traceBack(TreeNode A, TreeNode B) {
if (B==null)
return true;
if (A==null || A.val!=B.val)
return false;
//A.val == B.val 则继续递归
return traceBack(A.left, B.left) && traceBack(A.right, B.right);
}
}
题目:
请完成一个函数,输入一个二叉树,该函数输出它的镜像。
例如输入:
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]
限制:
0 <= 节点个数 <= 1000
题解:
从根节点开始,递归交换左右节点即可。
结束条件:节点为空。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public TreeNode mirrorTree(TreeNode root) {
TreeNode head = root;
reverse(head);
return root;
}
public void reverse(TreeNode node){
if (node==null)
return;
TreeNode temp = node.left;
node.left = node.right;
node.right = temp;
reverse(node.left);
reverse(node.right);
}
}
题目:
请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。
例如,二叉树 [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
题解:
思路
对称二叉树的定义:
对于头节点head
有:
head.left.val==head.right.val
:左右节点的值相等;head.left.left.val==head.right.right.val
:外层节点值相等;head.left.right.val==head.right.left.val
:内层节点值相等; head
/ \
left right
/ \ / \
l r l r
从头节点开始,从上到下开始递归,判断每对节点是否对称,从而判断是否为对称二叉树。
流程
isSymmetric
:
compare(root.left,head.right)
:子树的比较结果;compare
:compare(left.left, right.right)
compare(left.right, right.left)
内层节点相等
&& 外层节点相等
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public boolean isSymmetric(TreeNode root) {
if (root==null)
return true;
return compare(root.left, root.right);
}
public boolean compare(TreeNode left,TreeNode right){
if (left==right)
return true;
if (left==null || right==null||left.val!=right.val)
return false;
return compare(left.left, right.right)&&compare(left.right, right.left);
}
}
题目:
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。
示例 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]
限制:
0 <= matrix.length <= 100
0 <= matrix[i].length <= 100
class Solution {
public int[] spiralOrder(int[][] matrix) {
if (matrix.length==0)
return new int[0];
int x = 0;
int l = 0;
int t = 0;
int r = matrix[0].length-1;
int b = matrix.length-1;
int[] res = new int[matrix.length*matrix[0].length];
while (true){
for (int i = l; i <= r; i++) {
res[x++] = matrix[t][i];
}
if (++t>b)break;
for (int i = t; i <= b; i++) {
res[x++] = matrix[i][r];
}
if (--r<l)break;
for (int i = r; i >= l; i--) {
res[x++] = matrix[b][i];
}
if (--b<t)break;
for (int i = b; i >=t ; i--) {
res[x++] = matrix[i][l];
}
if (++l>r)break;
}
return res;
}
}
题目:
定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 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.
题解:
与题目:剑指 Offer 59 - II. 队列的最大值 非常相似。难点都在于如何保存当前结构中的最值。
队列使用 双端队列 保存最值。
本题目使用 栈 保存最值
class MinStack {
Stack<Integer> stack;
Stack<Integer> minStack;
//构造函数
public MinStack() {
stack = new Stack<>();
minStack = new Stack<>();
}
public void push(int x) {
stack.push(x);
if (minStack.isEmpty()) {
minStack.push(x);
} else if (x <= minStack.peek()) {
minStack.push(x);
}
}
public void pop() {
Integer pop = stack.pop();
if (pop.equals(minStack.peek()))
minStack.pop();
}
public int top() {
return stack.peek();
}
public int min() {
return minStack.peek();
}
}
题目:
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如,序列 {1,2,3,4,5} 是某栈的压栈序列,序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列,但 {4,3,5,1,2} 就不可能是该压栈序列的弹出序列。
示例 1:
输入:pushed = [1,2,3,4,5], popped = [4,5,3,2,1]
输出:true
解释:我们可以按以下顺序执行:
push(1), push(2), push(3), push(4), pop() -> 4,
push(5), pop() -> 5, pop() -> 3, pop() -> 2, pop() -> 1
示例 2:
输入:pushed = [1,2,3,4,5], popped = [4,3,5,1,2]
输出:false
解释:1 不能在 2 之前弹出。
提示:
0 <= pushed.length == popped.length <= 1000
0 <= pushed[i], popped[i] < 1000
pushed 是 popped 的排列。
题解:
考虑借用一个辅助栈 stack,模拟 压入 / 弹出操作的排列。根据是否模拟成功,即可得到结果。
入栈操作: 按照压栈序列的顺序执行。
出栈操作: 每次入栈后,循环判断 “栈顶元素 == 弹出序列的当前元素” 是否成立,将符合弹出序列顺序的栈顶元素全部弹出。
class Solution {
public boolean validateStackSequences(int[] pushed, int[] popped) {
int i = 0, j = 0;
Stack<Integer> stack = new Stack<>();
while (i <= pushed.length - 1) {
//1.按入栈顺序压入
stack.push(pushed[i++]);
//2.循环判断栈顶元素是否和出栈元素一致,一致则出栈
while (!stack.isEmpty() && stack.peek() == popped[j]) {
stack.pop();
j++;
}
}
//3.全部出栈则模拟成功,否则失败。
return stack.isEmpty();
}
}
题目:
从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。
例如:
给定二叉树: [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回:
[3,9,20,15,7]
提示:
节点总数 <= 1000
题解:
题目要求的二叉树的 从上至下 打印(即按层打印),又称为二叉树的 广度优先搜索(BFS)。
BFS 通常借助 队列 的先入先出特性来实现(从上到下,从左到右)。
BFS 循环: 当队列 queue 为空时跳出;
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public int[] levelOrder(TreeNode root) {
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
ArrayList<Integer> list = new ArrayList<>();
while (!queue.isEmpty()){
TreeNode node = queue.poll();
if (node!=null){
list.add(node.val);
queue.offer(node.left);
queue.offer(node.right);
}
}
int[] res = new int[list.size()];
for (int i = 0; i < list.size(); i++) {
res[i] = list.get(i);
}
return res;
}
}
题目:
从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。
例如:
给定二叉树: [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回其层次遍历结果:
[
[3],
[9,20],
[15,7]
]
题解:
当前层打印循环: 循环次数为当前层节点数(即队列 queue 长度);
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
Queue<TreeNode> queue = new LinkedList<>();
if (root!=null)
queue.add(root);
List<List<Integer>> list = new ArrayList<>();
while (!queue.isEmpty()) {
ArrayList<Integer> list1 = new ArrayList<>();
for (int i = queue.size(); i > 0; i--) {
TreeNode node = queue.poll();
if(node.left != null) queue.add(node.left);
if(node.right != null) queue.add(node.right);
list1.add(node.val);
}
list.add(list1);
}
return list;
}
}
*题目:*请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。
例如:
给定二叉树: [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回其层次遍历结果:
[
[3],
[20,9],
[15,7]
]
题解:
树的层序遍历借助queue即可。
本题的难点在于:
①每层节点数控制;
②之字型插入顺序;
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
ArrayList<List<Integer>> res = new ArrayList<>();
Queue<TreeNode> queue = new LinkedList<>();
if (root!=null)queue.add(root);
while (!queue.isEmpty()){
LinkedList<Integer> tmp = new LinkedList<>();
//①:这个for循环设置为倒遍历,是因为 queue.size() 会随着 queue.poll()变化。只有第一次的值可以反映树这一层的节点个数。 如果放在结束条件,i
for (int i = queue.size(); i >0 ; i--) {
TreeNode node = queue.poll();
//②:在层序遍历的基础上改变了添加方式。如果是偶数层(从1开始)则倒着插,实现倒叙
if (res.size()%2==0)tmp.addLast(node.val);
//奇数层,则正着插
else tmp.addFirst(node.val);
if (node.left!=null)queue.add(node.left);
if (node.right!=null)queue.add(node.right);
}
res.add(tmp);
}
return res;
}
}
题目:
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false。假设输入的数组的任意两个数字都互不相同。
参考以下这颗二叉搜索树:
5
/ \
2 6
/ \
1 3
示例 1:
输入: [1,6,3,2,5]
输出: false
示例 2:
输入: [1,3,2,6,5]
输出: true
提示:
数组长度 <= 1000
题解:
后序遍历定义: [ 左子树 | 右子树 | 根节点 ] ,即遍历顺序为 “左、右、根” 。
二叉搜索树定义: 左子树中所有节点的值 << 根节点的值;右子树中所有节点的值 >> 根节点的值;其左、右子树也分别为二叉搜索树。
分治思想:
终止条件: 当 left>=right,说明此子树节点数量 ≤1 ,无需判别正确性,因此直接返回 true;
迭代过程:
class Solution {
public boolean verifyPostorder(int[] postorder) {
return recur(postorder, 0, postorder.length - 1);
}
public boolean recur(int[] postorder, int left ,int right){
if (left>=right)
return true;
int p = left;
while (postorder[p]<postorder[right]){
p++;
}
int middle = p;
while (postorder[p]>postorder[right]){
p++;
}
return p== right && recur(postorder, left, middle-1)
&& recur(postorder, middle, right-1);
}
}
题目:
输入一棵二叉树和一个整数,打印出二叉树中节点值的和为输入整数的所有路径。从树的根节点开始往下一直到叶节点所经过的节点形成一条路径。
示例:
给定如下二叉树,以及目标和 target = 22,
5
/ \
4 8
/ / \
11 13 4
/ \ / \
7 2 5 1
返回:
[
[5,4,11,2],
[5,8,4,5]
]
题解:
本问题是典型的二叉树方案搜索问题,使用回溯法解决,其包括 先序遍历+路径记录
先序遍历:按照“根、左、右”的顺序,遍历所有节点。
路径记录:在先序遍历中,记录从根节点到当前节点的路径。当路径为①根节点到叶节点形成的路径②各节点值的和等于目标值target。则将此路径加入结果列表。
递归工作:
class Solution {
LinkedList<List<Integer>> res = new LinkedList<>();
LinkedList<Integer> path = new LinkedList<>();
public List<List<Integer>> pathSum(TreeNode root, int target) {
traceBack(root, target);
return res;
}
private void traceBack(TreeNode root, int target) {
//情况1:已经达到最深处,则返回
if (root==null)return; //如果目标值减为0,且已达到树的最深处
//情况2:没有达到最深处,则累计当前节点的值
target-=root.val;
path.add(root.val);
//每次递归都判断是否已经满足题目条件
if (target == 0 && root.left == null && root.right == null) {
List<Integer> tmp = new LinkedList<>(path);
res.add(tmp);
}
//递归过程
traceBack(root.left, target);
traceBack(root.right, target);
//-----上面是递归,下面是回溯
//能执行到这,说明开始回溯。回溯要先将当前节点的值去除
path.removeLast();
}
}
题目:
请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。
示例 1:
输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]
示例 2:
输入:head = [[1,1],[2,1]]
输出:[[1,1],[2,1]]
示例 3:
输入:head = [[3,null],[3,0],[3,null]]
输出:[[3,null],[3,0],[3,null]]
示例 4:
输入:head = []
输出:[]
解释:给定的链表为空(空指针),因此返回 null。
提示:
-10000 <= Node.val <= 10000
Node.random 为空(null)或指向链表中的节点。
节点数目不超过 1000 。
题解:
利用哈希表的查询特点,考虑构建 原链表节点 和 新链表对应节点 的键值对映射关系,再遍历构建新链表各节点的 next 和 random 引用指向即可。
/*
// Definition for a Node.
class Node {
int val;
Node next;
Node random;
public Node(int val) {
this.val = val;
this.next = null;
this.random = null;
}
}
*/
class Solution {
public Node copyRandomList(Node head) {
if (head == null)
return head;
Node tmp = head;
HashMap<Node, Node> map = new HashMap<>();
while (tmp!=null){
map.put(tmp,new Node(tmp.val));
tmp = tmp.next;
}
tmp = head;
while (tmp!=null){
map.get(tmp).next = map.get(tmp.next);
map.get(tmp).random = map.get(tmp.random);
tmp = tmp.next;
}
return map.get(head);
}
}
题目:
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。
为了让您更好地理解问题,以下面的二叉搜索树为例:
我们希望将这个二叉搜索树转化为双向循环链表。链表中的每个节点都有一个前驱和后继指针。对于双向循环链表,第一个节点的前驱是最后一个节点,最后一个节点的后继是第一个节点。
下图展示了上面的二叉搜索树转化成的链表。“head” 表示指向链表中有最小元素的节点。
特别地,我们希望可以就地完成转换操作。当转化完成以后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继。还需要返回链表中的第一个节点的指针。
题解:
使用二叉树的中序遍历访问树的各个节点 cur 。并在访问过程中构建 cur 和前驱节点 pre 的引用指向;中序遍历完成后,最后构建头节点 和 尾节点的引用指向即可。
递归流程:
class Solution {
Node head;
Node pre;
public Node treeToDoublyList(Node root) {
if (root==null)return null;
dfs(root);
head.left=pre;
pre.right=head;
return head;
}
private void dfs(Node cur) {
if (cur==null)return;
dfs(cur.left);
if (pre!=null){
pre.right=cur;
}else {
head = cur;
}
cur.left=pre;
pre= cur;
dfs(cur.right);
}
}
题目:
请实现两个函数,分别用来序列化和反序列化二叉树。
示例:
你可以将以下二叉树:
1
/ \
2 3
/ \
4 5
序列化为 "[1,2,3,null,null,4,5]"
题解:
序列化:
反序列化:
index。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
public class Codec {
// Encodes a tree to a single string.
public String serialize(TreeNode root) {
if (root == null)
return null;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
ArrayList<String> res = new ArrayList<>();
while (!queue.isEmpty()) {
TreeNode node = queue.poll();
if (node!=null){
res.add(String.valueOf(node.val));
queue.offer(node.left);
queue.offer(node.right);
}else {
res.add("null");
}
}
return res.toString();
}
// Decodes your encoded data to tree.
public TreeNode deserialize(String data) {
if(data==null){
return null;
}
String[] arr = data.substring(1, data.length() - 1).split(",");
Queue<TreeNode> queue = new LinkedList<>();
TreeNode head = new TreeNode(Integer.parseInt(arr[0]));
queue.offer(head);
int index = 1;
while (index<arr.length) {
TreeNode tmp = queue.poll();
String l = arr[index++];
if (!l.trim().equals("null")) {
tmp.left = new TreeNode(Integer.parseInt(l.trim()));
queue.offer(tmp.left);
}
String r = arr[index++];
if (!r.trim().equals("null")) {
tmp.right = new TreeNode(Integer.parseInt(r.trim()));
queue.offer(tmp.right);
}
}
return head;
}
}
// Your Codec object will be instantiated and called as such:
// Codec codec = new Codec();
// codec.deserialize(codec.serialize(root));
题目:
输入一个字符串,打印出该字符串中字符的所有排列。
示例:
输入:s = "abc"
输出:["abc","acb","bac","bca","cab","cba"]
你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。
题解:
对于"abc":(-表示待插入位置)
首先固定“a”:在“-a-”分别插入“b”得到[“ba”,“ab”]
读取[“ba”,“ab”]固定其中元素:在 ["-b-a-","-a-b-"]分别插入“c”,得到[“cba”,“bca”,“bac”, “cab”,“acb”,“abc”]
class Solution {
public String[] permutation(String s) {
if (s == null || s.equals(""))
return new String[0];
char[] chars = s.toCharArray();
//用于存储中间结果:比如["a","ba","ab","cba","bca","bac", "cab","acb","abc"]
ArrayList<String> res = new ArrayList<>();
res.add(String.valueOf(chars[0]));
//i:待插入的下一个元素索引
for (int i = 1; i < chars.length; i++) {
//j:用于遍历res中间结果
for (int j = res.size()-1; j >= 0; j--) {
//k:用于遍历 "-" 待插入位置。
for (int k = 0; k < i+1; k++) {
StringBuilder stringBuilder = new StringBuilder();
String s1 = res.get(j);
if (s1.length()==i){
stringBuilder.append(s1).insert(k, chars[i]);
res.add(stringBuilder.toString());
}
}
}
}
//根据长度过滤最终结果,同时去重
List<String> list = res.stream().filter(e -> e.length() == s.length()).distinct().collect(Collectors.toList());
String[] strings = new String[list.size()];
for (int i = 0; i < list.size(); i++) {
strings[i] = list.get(i);
}
return strings;
}
}
题目:
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
示例 1:
输入: [1, 2, 3, 2, 2, 2, 5, 4, 2]
输出: 2
你可以假设数组是非空的,并且给定的数组总是存在多数元素
题解:
哈希表统计法: 遍历数组 nums ,用 HashMap 统计各数字的数量,即可找出 众数 。此方法时间和空间复杂度均为 O(N) 。
class Solution {
public int majorityElement(int[] nums) {
HashMap<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
if (map.get(nums[i])==null){
map.put(nums[i], 1);
}else {
map.put(nums[i], map.get(nums[i])+1);
}
}
Integer integer = map.values().stream().max(Integer::compareTo).get();
for (Integer integer1 : map.keySet()) {
if (integer==map.get(integer1))
return integer1;
}
return 0;
}
}
题目:
输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。
示例 1:
输入:arr = [3,2,1], k = 2
输出:[1,2] 或者 [2,1]
示例 2:
输入:arr = [0,1,2,1], k = 1
输出:[0]
题解:
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
ArrayList<Integer> list = new ArrayList<>();
for (int j : arr) {
list.add(j);
}
Collections.sort(list);
int[] res = new int[k];
for (int i = 0; i < k; i++) {
res[i] = list.get(i);
}
return res;
}
}
题目:
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。
例如,
[2,3,4] 的中位数是 3
[2,3] 的中位数是 (2 + 3) / 2 = 2.5
设计一个支持以下两种操作的数据结构:
示例 1:
输入:
["MedianFinder","addNum","addNum","findMedian","addNum","findMedian"]
[[],[1],[2],[],[3],[]]
输出:[null,null,null,1.50000,null,2.00000]
示例 2:
输入:
["MedianFinder","addNum","findMedian","addNum","findMedian"]
[[],[2],[],[3],[]]
输出:[null,null,2.00000,null,2.50000]
题解:
优先队列PriorityQueue
的使用。
PriorityQueue
与先进先出队列Queue
的不同:
Comparator
。class MedianFinder {
Queue<Integer> A, B;
public MedianFinder() {
A = new PriorityQueue<Integer>();
B = new PriorityQueue<Integer>((x, y) -> (y - x));
}
public void addNum(int num) {
if(A.size() != B.size()) {
A.add(num);
B.add(A.poll());
} else {
B.add(num);
A.add(B.poll());
}
}
public double findMedian() {
return A.size()==B.size()?((A.peek()+ B.peek())/2.0):A.peek();
}
}
题目:
输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。
要求时间复杂度为O(n)。
示例1:
输入: nums = [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
题解:
动态规划。
状态定义: 设动态规划列表 dp,dp[i] 代表以元素 nums[i] 为结尾的连续子数组最大和。
为何定义最大和 dp[i]中必须包含元素 nums[i] :保证 dp[i] 递推到 dp[i+1] 的正确性;如果不包含 nums[i] ,递推时则不满足题目的 连续子数组 要求.
转移方程: 若 dp[i-1]<=0
,说明dp[i−1]
对 dp[i]
产生负贡献,即 dp[i-1] + nums[i]
还不如 nums[i]
本身大。
当dp[i - 1] > 0
时:执行 dp[i] = dp[i-1] + nums[i]
;
当 dp[i−1]≤0
时:执行 dp[i] = nums[i]
;
dp[0] = nums[0]
,即以 nums[0]
结尾的连续子数组最大和为nums[0]
。dp
列表中的最大值,代表全局最大值。解法1:
public int maxSubArray(int[] nums) {
int res = nums[0];
for (int i = 1; i < nums.length; i++) {
nums[i] += Math.max(nums[i-1],0);
res = Math.max(res, nums[i]);
}
return res;
}
解法2:
public int maxSubArray(int[] nums) {
if (nums==null || nums.length==0)
return 0;
if (nums.length==1)
return nums[0];
//有正数时,返回res
int res = 0;
//临时值:单个或连续元素的值
int tmp = 0;
//都是负数时,记录最小的一个
int special=nums[0];
for (int num : nums) {
tmp += num;
//如果为负数,会起反作用,直接抛弃掉
if (tmp <= 0) {
tmp = 0;
}
//记录最大值(单个、连续)
res = Math.max(tmp, res);
//记录(单个)
special = Math.max(special, num);
}
//全是负数时,special即为最大值
return res!=0?res:special;
}
题目:
输入一个整数 n ,求1~n这n个整数的十进制表示中1出现的次数。
例如,输入12,1~12这些整数中包含1 的数字有1、10、11和12,1一共出现了5次。
示例 1:
输入:n = 12
输出:5
示例 2:
输入:n = 13
输出:6
限制:
1 <= n < 2^31
class Solution {
public int countDigitOne(int n) {
int res =0,digit=1;
int high=n/10,cur=n%10,low=0;
while (high != 0 || cur!=0){
if (cur==0){
res+=high*digit;
}else if (cur==1){
res+=(high*digit+low+1);
}else {
res+=(high+1)*digit;
}
low += cur*digit;
cur = high%10;
high/=10;
digit*=10;
}
return res;
}
}
题目:
数字以0123456789101112131415…的格式序列化到一个字符序列中。在这个序列中,第5位(从下标0开始计数)是5,第13位是1,第19位是4,等等。
请写一个函数,求任意第n位对应的数字。
示例 1:
输入:n = 3
输出:3
示例 2:
输入:n = 11
输出:0
限制:
0 <= n < 2^31
class Solution {
public int findNthDigit(int n) {
int digit = 1;
long start = 1;
long count = 9;
while (n>count){
n-=count;
digit+=1;
start*=10;
count = 9*start*digit;
}
long num = start+(n-1)/digit;
return String.valueOf(num).charAt((n-1)%digit)-'0';
}
}
题目:
输入一个非负整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。
示例 1:
输入: [10,2]
输出: "102"
示例 2:
输入: [3,30,34,5,9]
输出: "3033459"
题解:
内置函数:list.sort((x, y) -> (x + y).compareTo(y + x));
的使用。
class Solution {
public String minNumber(int[] nums) {
ArrayList<String> list = new ArrayList<>();
for (int num : nums) {
list.add(String.valueOf(num));
}
list.sort((x, y) -> (x + y).compareTo(y + x));
StringBuilder res = new StringBuilder();
for (String s : list) {
res.append(s);
}
return res.toString();
}
}
题目:
给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。
示例 1:
输入: 12258
输出: 5
解释: 12258有5种不同的翻译,分别是"bccfi", "bwfi", "bczi", "mcfi"和"mzi"
class Solution {
public int translateNum(int num) {
String s = String.valueOf(num);
//无数字
int b = 1;
//第一位数字
int a = 1;
for(int i = 2; i <= s.length(); i++) {
String tmp = s.substring(i - 2, i);
int c = tmp.compareTo("10") >= 0 && tmp.compareTo("25") <= 0 ? a + b : a;
b = a;
a = c;
}
return a;
}
}
class Solution {
public int translateNum(int num) {
int[] ints = new int[String.valueOf(num).length()];
ints[0] = num/(int) Math.pow(10,String.valueOf(num).length()-1);
for (int i = 1; i <String.valueOf(num).length(); i++) {
ints[i] = num/(int) Math.pow(10,String.valueOf(num).length()-(i+1))%10;
}
if (ints.length<=1)
return ints.length;
int[] dp = new int[ints.length];
dp[0] = 1;
if (ints[0]==1 || (ints[0]==2 && ints [1]<=5)){
dp[1] = 2;
}else {
dp[1] = 1;
}
int index = 2;
while (index<ints.length){
if (ints[index-1]==1 || (ints[index-1]==2 && ints [index]<=5)){
dp[index] = dp[index-1]+dp[index-2];
}else {
dp[index] = dp[index-1];
}
index++;
}
return dp[dp.length-1];
}
}
题目:
在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物?
示例 1:
输入:
[
[1,3,1],
[1,5,1],
[4,2,1]
]
输出: 12
解释: 路径 1→3→5→2→1 可以拿到最多价值的礼物
class Solution {
public int maxValue(int[][] grid) {
int m = grid.length;
int n = grid[0].length;
int[][] dp = new int[m][n];
dp[0][0] = grid[0][0];
//初始化行边缘
for (int i = 1; i < m; i++) {
dp[i][0] = dp[i-1][0]+grid[i][0];
}
//初始化列边缘
for (int j = 1; j < n; j++) {
dp[0][j] = dp[0][j-1]+grid[0][j];
}
//状态转移方程: dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1])+grid[i][j];
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1])+grid[i][j];
}
}
return dp[m-1][n-1];
}
}
题目:
请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。
示例 1:
输入: "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:
输入: "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:
输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
题解:
从左向右遍历字符串s,遇到相同的元素时,去除左边的重复元素及它以前的所有元素(多次执行else分支)。
class Solution {
public int lengthOfLongestSubstring(String s) {
if (s==null || s.length()==0)
return 0;
//临时保存最长的字符串。
StringBuilder builder = new StringBuilder();
//记录最大长度
int res = 0;
//遍历s
int i = 0;
while (i<s.length()){
//如果没有重复元素存在,就不断添加
if (builder.indexOf(String.valueOf(s.charAt(i)))==-1){
builder.append(s.charAt(i));
res = Math.max(res, builder.length());
i++;
}else {
//注意,这里并没有i++;因此下次循环还是判断 s.charAt(i) 有没有在builder 重复存在。
//举例:“abcfbe” 元素b重复,在遍历到“abcf”准备添加“b”时,则需要截取“cf”。else会被执行两次,每次删除最左边的元素。第一次删除“a”,第二次删除“b”。
builder = new StringBuilder(builder.substring(1, builder.length()));
}
}
return res;
}
}
题目:
我们把只包含质因子 2、3 和 5 的数称作丑数(Ugly Number)。求按从小到大的顺序的第 n 个丑数。
示例:
输入: n = 10
输出: 12
解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。
说明:
1 是丑数。
n 不超过1690。
class Solution {
public int nthUglyNumber(int n) {
int[] dp = new int[n];
dp[0] = 1;
int a=0,b=0,c=0,i=1;
while (i<n){
dp[i] = Math.min(dp[a]*2,Math.min(dp[b]*3,dp[c]*5));
if (dp[i]==dp[a]*2)
a++;
if (dp[i]==dp[b]*3)
b++;
if (dp[i]==dp[c]*5)
c++;
i++;
}
return dp[n-1];
}
}
题目:
在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。 s 只包含小写字母。
示例:
s = "abaccdeff"
返回 "b"
s = ""
返回 " "
题解:
class Solution {
public char firstUniqChar(String s) {
if (s==null || s.equals(" "))
return ' ';
for (int i = 0; i < s.length(); i++) {
if (s.indexOf(s.charAt(i))==s.lastIndexOf(s.charAt(i)))
return s.charAt(i);
}
return ' ';
}
}
题目:
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
示例 1:
输入: [7,5,6,4]
输出: 5
题解:
分治思想+归并排序传送门。
{1,3,5,2,4,6}
为例。 将数组分成两部分: ① 3 5 | ② 4 6 --> {}
对两部分进行合并的过程中计算逆序对的数量 count。
①<②则将①放入新数组: ③ 5 | ② 4 6 -->{1}
1 没有和任何元素构成逆序对。
③>②则将②放入新数组: ③ 5 | ④ 6 -->{1,2}
2和{3,5}构成逆序对,count = 2;
③<④则将③放入新数组: ⑤ | ④ 6 -->{1,2,3}
3 没有和任何元素构成逆序对。
⑤>④则将④放入新数组: ⑤ | ⑥ -->{1,2,3,4}
4和{5}构成逆序对, count=count+1=3;
同理⑤、⑥加入新数组,没有增加逆序对。 | -->{1,2,3,4,5,6}
总结:如果可以将一个数组分成两个有序数组。在将两个子数组合并的过程中,逆序对儿的计算方式为:
if(left>right){
count += 左边数组剩余元素数量
}
但是,如何将一个任意数组分成有序的子数组呢。需要使用到归并排序中的分解、归并。
2. 「归并排序」是分治思想的典型应用,它包含这样三个步骤:
- 分解:待排序的区间为 [l, r],令 m = (l+r)/2,我们把 [l, r] 分成 [l, m] 和 [m + 1, r]
- 解决:使用归并排序递归地排序两个子序列
- 合并: 把两个已经排好序的子序列 [l, m] 和 [m + 1, r] 合并起来
3. 由此可以看到,归并排序会把任意数组分解成只包含一个元素的子数组,而单个元素必然是有序数组。归并的过程则是同步骤1所示,计算所有逆序对的数量。
class Solution {
public int reversePairs(int[] nums) {
int length = nums.length;
if (length<2)
return 0;
int[] tmp = new int[length];
return reversePairs(nums,0,length-1,tmp);
}
//分解过程
public int reversePairs(int[] nums,int left,int right,int[] tmp){
//分解结束条件:左右边界相遇(即只有一个元素)
if (left==right)
return 0;
//计算中点
int mid = left + (right-left)/2;
//向左递归分解
int leftPairs = reversePairs(nums,left,mid,tmp);
//向右递归分解
int rightPairs = reversePairs(nums,mid+1,right,tmp);
//分解结束后开始合并
int crossPairs = crossPairs(nums,left,mid,right,tmp);
//返回所有合并过程中计算的逆序对数量
return leftPairs+rightPairs+crossPairs;
}
//合并
public int crossPairs(int[] nums,int left,int mid,int right,int[] tmp){
//复制一个临时数组
for (int i = left; i < right+1; i++) {
tmp[i] = nums[i];
}
int i=left,j=mid+1,count=0;
for (int k = left; k < right+1; k++) {
//左边数组遍历完,将右边剩余的元素直接添加
if (i==mid+1){
nums[k]=tmp[j];
j++;
//右边数组遍历完,将左边剩余的元素直接添加
}else if (j==right+1){
nums[k]=tmp[i];
i++;
//左边的元素小,直接添加,不会有逆序对产生
}else if (tmp[i]<=tmp[j]){
nums[k]=tmp[i];
i++;
}else {
//右边元素小,逆序对的数量等于左边数组的剩余元素数量
nums[k]=tmp[j];
j++;
count+=(mid-i+1);
}
}
return count;
}
}
题目:
输入两个链表,找出它们的第一个公共节点。
注意:
如果两个链表没有交点,返回 null.
在返回结果后,两个链表仍须保持原有的结构。
可假定整个链表结构中没有循环。
程序尽量满足 O(n) 时间复杂度,且仅用 O(1) 内存。
题解:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ArrayList<ListNode> listNodes = new ArrayList<>();
while (headA!=null){
listNodes.add(headA);
headA=headA.next;
}
while (headB!=null){
if (listNodes.contains(headB)){
return headB;
}
headB=headB.next;
}
return null;
}
}
题目:
统计一个数字在排序数组中出现的次数。
示例 1:
输入: nums = [5,7,7,8,8,10], target = 8
输出: 2
示例 2:
输入: nums = [5,7,7,8,8,10], target = 6
输出: 0
题解:
class Solution {
public int search(int[] nums, int target) {
int i =0;
while (i<nums.length && nums[i]!=target){
i++;
}
int j =i;
while (j<nums.length && nums[j]==target){
j++;
}
return j-i;
}
}
题目:
一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内。在范围0~n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。
示例 1:
输入: [0,1,3]
输出: 2
示例 2:
输入: [0,1,2,3,4,5,6,7,9]
输出: 8
class Solution {
public int missingNumber(int[] nums) {
for (int i = 0; i < nums.length; i++) {
if (i!=nums[i])
return i;
}
return nums.length;
}
}
题目:
给定一棵二叉搜索树,请找出其中第k大的节点。
示例 1:
输入: root = [3,1,4,null,2], k = 1
3
/ \
1 4
\
2
输出: 4
示例 2:
输入: root = [5,3,6,2,4,null,null,1], k = 3
5
/ \
3 6
/ \
2 4
/
1
输出: 4
题解:
搜索二叉树的中序遍历,遍历结果是有序的。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public int kthLargest(TreeNode root, int k) {
ArrayList<Integer> list = new ArrayList<>();
traceBack(list,root);
return list.get(list.size()-k);
}
public void traceBack(List<Integer> list,TreeNode node){
if (node==null)
return;
traceBack(list, node.left);
list.add(node.val);
traceBack(list, node.right);
}
}
题目:
输入一棵二叉树的根节点,求该树的深度。从根节点到叶节点依次经过的节点(含根、叶节点)形成树的一条路径,最长路径的长度为树的深度。
例如:
给定二叉树 [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回它的最大深度 3 。
题解:
方法一:
后续遍历法。
此树的深度和其左(右)子树的深度之间的关系。显然,此树的深度 等于 左子树的深度
与 右子树的深度
中的 最大值 +1
。
方法二:
层序遍历法。
class Solution {
//后续遍历法
public int maxDepth(TreeNode root) {
if (root==null)
return 0;
return Math.max(maxDepth(root.left), maxDepth(root.right))+1;
}
//层序遍历法
public int maxDepth(TreeNode root) {
if (root==null)
return 0;
Queue<TreeNode> queue = new LinkedList<>();
Queue<TreeNode> tmp ;
queue.add(root);
int res=0;
while (!queue.isEmpty()){
tmp = new LinkedList<>();
for(TreeNode node : queue) {
if(node.left != null) tmp.add(node.left);
if(node.right != null) tmp.add(node.right);
}
queue=tmp;
res++;
}
return res;
}
}
题目:
输入一棵二叉树的根节点,判断该树是不是平衡二叉树。如果某二叉树中任意节点的左右子树的深度相差不超过1,那么它就是一棵平衡二叉树。
示例 1:
给定二叉树 [3,9,20,null,null,15,7]
3
/ \
9 20
/ \
15 7
返回 true 。
示例 2:
给定二叉树 [1,2,2,3,3,null,null,4,4]
1
/ \
2 2
/ \
3 3
/ \
4 4
返回 false 。
题解:
树深度的计算方法:
此树的深度和其左(右)子树的深度之间的关系。显然,此树的深度 等于 左子树的深度
与 右子树的深度
中的 最大值 +1
。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public boolean isBalanced(TreeNode root) {
if (root==null)return true;
return Math.abs(maxDep(root.left)-maxDep(root.right))<=1&&isBalanced(root.left)&&isBalanced(root.right);
}
//获取以当前节点为根节点的树的最大深度。
public int maxDep(TreeNode node){
if (node==null)
return 0;
return Math.max(maxDep(node.left),maxDep(node.right))+1;
}
}
题目:
一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
示例 1:
输入:nums = [4,1,4,6]
输出:[1,6] 或 [6,1]
示例 2:
输入:nums = [1,2,10,4,1,4,3,3]
输出:[2,10] 或 [10,2]
题解:位异或运算
异或
:二进制中,相同为0,不同为1;
异或的特点
:
异或
,最终得到只出现1次的那个数。举个例子:[4,1,4,2,2]
因为异或满足交换律,所以执行异或前可以把数组可以变成
[4,4,2,2,1]
开始执行全员异或:
4 异或 4 = 0
0 异或 2 = 2
2 异或 2 = 0
0 异或 1 = 1
最终异或的结果 = 1 ,即为仅出现1次的数字。
本题中数组里面有两个仅出现1次的数字,如果直接使用上述全员异或的方法,最终只能得到这两个数异或的结果,二者分不开了,不能得到两个数本身。
解决方法
:
某个条件
将这两个不同的数分开,然后再分别执行异或呢?位
。拿它当作判断条件,将原数组中的元素进行分组。则这两个不同的数必然也会被分配到不同的子数组中。再对两个子数组分别进行全员异或,就可以直接得到这两个数本身了。[4,1,4,6]
按照上面的步骤,先对整个数组进行一次全员异或:
4 异或 1 异或 4 异或 6 = 1 异或 6
1 的二进制: 001
6 的二进制: 110
1 异或 6 的结果: 111
寻找两个数二进制中必然不同的位,因为 0 异或 1 = 1,所以我们只需要在 (1 异或 6的结果)中从右向左找到一个为 1 的位即可(从右向左第 1 位)。
根据二进制从右向左第一位是否为 1 ,对原数组中的元素进行分组并全员异或:
组1(二进制从右向左第一位为1):1 = 1
组2(二进制从右向左第一位为0):4 异或 4 异或 6 = 6
最终得到结果:1、6
class Solution {
public int[] singleNumbers(int[] nums) {
int x = 0, y = 0, n = 0, m = 1;
for(int num : nums) // 1. 遍历异或
n ^= num;
while((n & m) == 0) // 2. 循环左移,计算 m
m <<= 1;
for(int num: nums) {
// 3. 遍历 nums 分组
if((num & m) != 0) x ^= num; // 4. 当 num & m != 0
else y ^= num; // 4. 当 num & m == 0
}
return new int[] {
x, y}; // 5. 返回出现一次的数字
}
}
题目:
在一个数组 nums 中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。
示例 1:
输入:nums = [3,4,3,3]
输出:4
示例 2:
输入:nums = [9,1,7,9,7,9,7]
输出:1
题解:
二进制与位运算;
举例:
[1,5,5,5]
数组中元素的二进制为:
1: 0 0 0 1
5: 0 1 0 1
5: 0 1 0 1
5: 0 1 0 1
如果将所有元素的二进制逐位求和:
0 3 0 4
再逐位取余3:
0 0 0 1 = 十进制的 1
就可以得到仅出现 1 次的元素
时间复杂度为O(N),空间复杂度为O(1);
class Solution {
public int singleNumber(int[] nums) {
//Int型整数 最大为 2 的 32 次方,创建一个32长度的数组存储 位 的和。
int[] ints = new int[32];
for (int num : nums) {
for (int i = 0; i < 32; i++) {
ints[i]+=num&1;
//无符号右移,再与1做与运算,可以获取逐 位 的值。
num>>>=1;
}
}
//如果将3改为其他的数字,可以解决只有一个1出现1次,其他数出现m次的情况,
int res = 0 , m=3;
for (int i = 0; i < 32; i++) {
//0的左移还是0;
res<<=1;
//从最高位开始,0与取余结果的 或运算 可以获取每 位 的值。同时配合左移,从高位依次赋值到低位。最终res就是返回结果。
res |= ints[31-i] % m;
}
return res;
}
}
题目:
输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得它们的和正好是s。如果有多对数字的和等于s,则输出任意一对即可。
示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[2,7] 或者 [7,2]
示例 2:
输入:nums = [10,26,30,31,47,60], target = 40
输出:[10,30] 或者 [30,10]
题解:
双指针法。将两个索引 left
、right
指向有序数组的首尾。
nums[left]+nums[right]==target
:则找到;nums[left]+nums[right]>target
:说明和过大,nums[left]
已经是小值了,需要减小nums[right]
;nums[left]+nums[right]:说明和过小,nums[right]
已经是大值了,需要增大nums[left]
;
class Solution {
public int[] twoSum(int[] nums, int target) {
int left = 0, right = nums.length-1;
while (left<right){
if (nums[left]+nums[right]==target){
break;
}else if (nums[left]+nums[right]>target){
right--;
}else {
left++;
}
}
return new int[]{
nums[left],nums[right]};
}
}
题目:
输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。
序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。
示例 1:
输入:target = 9
输出:[[2,3,4],[4,5]]
示例 2:
输入:target = 15
输出:[[1,2,3,4,5],[4,5,6],[7,8]]
题解:
滑动窗口。
设窗口左边界left
,有边界right
,窗口内元素和res
.
res=target
,则记录窗口内元素。res>target
,说明窗口内元素和过大,需要减少元素,向右移动left
res,说明窗口内元素和过小,需要增加元素,向右移动right
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){
int[] ints = new int[right - left];
for (int i = 0; i < ints.length; i++) {
ints[i] = i+left;
}
res.add(ints);
sum+=right++;
}
else if (sum<target){
sum+=right++;
}else{
sum-=left++;
}
}
return res.toArray(new int[res.size()][]);
}
}
题目:
输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。为简单起见,标点符号和普通字母一样处理。例如输入字符串"I am a student. “,则输出"student. a am I”。
示例 1:
输入: "the sky is blue"
输出: "blue is sky the"
示例 2:
输入: " hello world! "
输出: "world! hello"
解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
示例 3:
输入: "a good example"
输出: "example good a"
解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。
说明:
无空格字符构成一个单词。
输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。
题解:
class Solution {
public String reverseWords(String s) {
if(s.trim().length()==0)
return "";
String[] strings = s.trim().split(" ");
StringBuilder builder = new StringBuilder();
for (int i = strings.length-1; i >=0; i--) {
builder.append(strings[i].trim());
if (strings[i].trim().length()>0){
builder.append(" ");
}
}
return builder.substring(0, builder.length()-1);
}
}
题目:
字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。
示例 1:
输入: s = "abcdefg", k = 2
输出: "cdefgab"
示例 2:
输入: s = "lrloseumgh", k = 6
输出: "umghlrlose"
题解:
class Solution {
public String reverseLeftWords(String s, int n) {
return s.substring(n) + s.substring(0, n);
}
}
题目:
给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值。
示例:
输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
输出: [3,3,5,5,6,7]
解释:
滑动窗口的位置 最大值
--------------- -----
[1 3 -1] -3 5 3 6 7 3
1 [3 -1 -3] 5 3 6 7 3
1 3 [-1 -3 5] 3 6 7 5
1 3 -1 [-3 5 3] 6 7 5
1 3 -1 -3 [5 3 6] 7 6
1 3 -1 -3 5 [3 6 7] 7
题解:
方法一:暴力解法
方法二:单调队列
//暴力解法
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
if (nums.length<1)
return new int[0];
int left =0,right=left+k;
ArrayList<Integer> list = new ArrayList<>();
while (right<=nums.length){
int[] ints = Arrays.copyOfRange(nums, left, right);
int max = ints[0];
for (int anInt : ints) {
max=Math.max(max, anInt);
}
list.add(max);
left++;
right++;
}
int[] res = new int[list.size()];
for (int i = 0; i < list.size(); i++) {
res[i] = list.get(i);
}
return res;
}
}
题目:
请定义一个队列并实现函数 max_value 得到队列里的最大值,要求函数max_value、push_back 和 pop_front 的均摊时间复杂度都是O(1)。
若队列为空,pop_front 和 max_value 需要返回 -1
示例 1:
输入:
["MaxQueue","push_back","push_back","max_value","pop_front","max_value"]
[[],[1],[2],[],[],[]]
输出: [null,null,null,2,1,2]
示例 2:
输入:
["MaxQueue","pop_front","max_value"]
[[],[],[]]
输出: [null,-1,-1]
题解:
解决办法:用空间换时间,上述方法只用单个值保存了最大值导致更新失败。如果保存一个递减序列,如果最大值出队列,则由后续元素”顶上“,就可以解决最大元素出队列后无法更新的问题。
举个例子:
初始化一个queue存储队列元素,初始化一个deque保存元素中的最大值(递减排列)。
开始:
queue : 4
deuqe: 4
入队第一个元素,必然最大值也是4;
queue: 5、4
deque: 5
入队第二个元素5,此时最大元素应该是5;deque的操作如下:
新来的5 和 deque中队尾的 4 比较,5大:4出队抛弃,5进队列(从队尾)。
这步可以这么理解,queue进出满足”先入先出“的规则,5比4后来,所以在5离开之前,4必然已经出队了。那么在5离开之前,5一直都是最大值。那么deque也就不用再保存4了,因为有5在它后面出队,4永远没有机会再当最大值。
queue:3、5、4
deque: 5、3
入队第三个元素3,此时最大元素还是5;deque操作如下:
新来的3 比 deque队尾的5小,因为deque递减保存。它只能跟在5的后面。
这步可以这么理解,老大哥5比3来的早,队列先进先出,5出去以后,3还是有机会当最大值的。所以3必须入deque。
queue: 3、5
deque:5、3
出队队首元素4、此时最大值仍然是5;
queue:3
deque:3
这两步的deque操作如下:
出队的元素跟deque队首(当前最大值)比较,看看是不是最大值自己要离开了。如果不是”大哥“要走了,那么deque不变,大哥仍然是大哥。如果是”大哥“要走了,那么deque队首出队,新的deque队首就变成了原deque的第二个元素(3)。
总结:
使用queue
保存队列元素。
使用deque
保存最大值序列,队首元素为当前”大哥“,后续元素为候选大哥。不过,新来元素如果很大,就会把比它小的候选元素都挤出候选队列。”年轻人还厉害,老人就没机会了!“
class MaxQueue {
private Queue<Integer> queue;
private Deque<Integer> deque;
public MaxQueue() {
queue = new LinkedList<>();
deque = new LinkedList<>();
}
public int max_value() {
return deque.isEmpty()?-1:deque.peekFirst();
}
public void push_back(int value) {
queue.offer(value);
while (!deque.isEmpty()&&deque.peekLast()<value)
deque.pollLast();
deque.offer(value);
}
public int pop_front() {
if (queue.isEmpty())
return -1;
if (queue.peek().equals(deque.peekFirst()))
deque.pollFirst();
return queue.poll();
}
}
题目:
把n个骰子扔在地上,所有骰子朝上一面的点数之和为s。输入n,打印出s的所有可能的值出现的概率。
你需要用一个浮点数数组返回答案,其中第 i 个元素代表这 n 个骰子所能掷出的点数集合中第 i 小的那个的概率。
示例 1:
输入: 1
输出: [0.16667,0.16667,0.16667,0.16667,0.16667,0.16667]
示例 2:
输入: 2
输出: [0.02778,0.05556,0.08333,0.11111,0.13889,0.16667,0.13889,0.11111,0.08333,0.05556,0.02778]
题解:
动态规划。
三步骤:
for (第n枚骰子的点数 i = 1; i <= 6; i ++) {
dp[n][j] += dp[n-1][j - i]
}
for (int i = 1; i <= 6; i ++) {
dp[1][i] = 1;
}
class Solution {
public double[] dicesProbability(int n) {
int[][] dp = new int[n + 1][6 * n + 1];
for (int i = 1; i <= 6; i++) {
dp[1][i] = 1;
}
for (int i = 2; i <= n; i++) {
for (int j = i; j <=6*i ; j++) {
for (int k = 1; k <=6 && k<j ; k++) {
dp[i][j] += dp[i-1][j-k];
}
}
}
double[] ans = new double[6 * n - n + 1];
for(int i = n; i <= 6 * n; i++)
ans[i - n] = ((double)dp[n][i]) / (Math.pow(6,n));
return ans;
}
}
题目:
从扑克牌中随机抽5张牌,判断是不是一个顺子,即这5张牌是不是连续的。2~10为数字本身,A为1,J为11,Q为12,K为13,而大、小王为 0 ,可以看成任意数字。A 不能视为 14。
示例 1:
输入: [1,2,3,4,5]
输出: True
示例 2:
输入: [0,0,1,2,5]
输出: True
题解:
0可以作为任何数填充。 所以只需要判断拥有的0是否比需要的0更多。
特殊情况:有相同的数则立即返回false。
class Solution {
public boolean isStraight(int[] nums) {
//1.排序
Arrays.sort(nums);
//2.统计 0 的个数
int len = 0;
for (int num : nums) {
if (num==0)len++;
}
//3.判断连续(从第一个非0开始判断)
int dif = 0;
for (int i = len+1; i < nums.length; i++) {
int s = nums[i] - nums[i - 1];
if (s==0)return false;
dif +=(s-1);
}
return len>=dif;
}
}
题目:
0,1,···,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字(删除后从下一个数字开始计数)。求出这个圆圈里剩下的最后一个数字。
例如,0、1、2、3、4这5个数字组成一个圆圈,从数字0开始每次删除第3个数字,则删除的前4个数字依次是2、0、4、1,因此最后剩下的数字是3。
示例 1:
输入: n = 5, m = 3
输出: 3
示例 2:
输入: n = 10, m = 17
输出: 2
class Solution {
public int lastRemaining(int n, int m) {
int[] dp = new int[n+1];
dp[1] = 0;
for (int i = 2; i < n+1; i++) {
dp[i] = (dp[i-1]+m)%i;
}
return dp[n];
}
}
题目:
假设把某股票的价格按照时间先后顺序存储在数组中,请问买卖该股票一次可能获得的最大利润是多少?
示例 1:
输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。
示例 2:
输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
题解:
动态规划:
dp[i]
第i
天卖出时的最大利润.dp[i]
等于前i−1
日最大利润 dp[i-1]
和第i
日卖出的最大利润中的最大值。 public int maxProfit(int[] prices) {
int min = Integer.MAX_VALUE,res=0;
for (int price:prices) {
min = Math.min(min, price);
res = Math.max(res, price-min);
}
return res;
}
题目:
求 1+2+…+n ,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
题解:
用递归代替循环。用逻辑判断的短路效应代替条件判断。
class Solution {
public int sumNums(int n) {
boolean x = n > 1 && (n += sumNums(n - 1)) > 0;
return n;
}
}
题目:
写一个函数,求两个整数之和,要求在函数体内不得使用 “+”、“-”、“*”、“/” 四则运算符号。
示例:
输入: a = 1, b = 1
输出: 2
题解:
参考连接
^:无进位相加结果;
二进制都为1才进位。
&:找出都为1的位置(进位位置)
<<:左移,得到进位值。
最后循环执行:进位值
+无进位值
class Solution {
public int add(int a, int b) {
while (b!=0){
int tmp = a^b;
b = (a&b)<<1;
a = tmp;
}
return a;
}
}
题目:
给定一个数组 A[0,1,…,n-1],请构建一个数组 B[0,1,…,n-1],其中 B[i] 的值是数组 A 中除了下标 i 以外的元素的积, 即 B[i]=A[0]×A[1]×…×A[i-1]×A[i+1]×…×A[n-1]。不能使用除法。
示例:
输入: [1,2,3,4,5]
输出: [120,60,40,30,24]
题解:
用两个数组分别存储左右三角形的每行乘积。再组合起来。
class Solution {
public int[] constructArr(int[] a) {
if (a.length<1)return new int[0];
//第一个数组维护左下三角形
int[] arr1 = new int[a.length];arr1[0]=1;
//第二个数组维护右下三角型
int[] arr2 = new int[a.length];arr2[a.length-1]=1;
//遍历存储左三角
for (int i = 1; i < a.length; i++) {
arr1[i] = a[i-1]*arr1[i-1];
}
//遍历存储右三角
for (int i = a.length-2; i >=0 ; i--) {
arr2[i] = arr2[i+1]*a[i+1];
}
//组合出结果
int[] res = new int[a.length];
for (int i = 0; i < res.length; i++) {
res[i] = arr1[i]*arr2[i];
}
return res;
}
}
题目:
给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
例如,给定如下二叉搜索树: root = [6,2,8,0,4,7,9,null,null,3,5]
示例 1:
输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
输出: 6
解释: 节点 2 和节点 8 的最近公共祖先是 6。
示例 2:
输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4
输出: 2
解释: 节点 2 和节点 4 的最近公共祖先是 2, 因为根据定义最近公共祖先节点可以为节点本身。
题解:
若 root.val < p.val,则 p 在 root 右子树 中;
若 root.val > p.val,则 p 在 root 左子树 中;
若 root.val = p.val,则 p 和 root 指向 同一节点 。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
while (root!=null){
if (p.val>root.val&&q.val>root.val){
root=root.right;
}else if (p.val<root.val&&q.val<root.val){
root = root.left;
}else {
break;
}
}
return root;
}
}
题目:
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4]
示例 1:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出: 3
解释: 节点 5 和节点 1 的最近公共祖先是节点 3。
示例 2:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出: 5
解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身。
题解:
见注释。
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root==null)return null;//如果根节点为null,则直接返回null
if(root==p||root==q)return root;//如果根节点中有p 或 q。则直接返回(自己也可以当自己的父亲节点)
//开始遍历左子树,p、q、null先碰到谁就返回谁
TreeNode left = lowestCommonAncestor(root.left, p, q);
//开始遍历右子树,p、q、null先碰到谁就返回谁
TreeNode right = lowestCommonAncestor(root.right, p, q);
//如果在左子树中 p和 q都找不到,则 p和 q一定都在右子树中,右子树中先遍历到的那个就是最近公共祖先(一个节点也可以是它自己的祖先)
if (left==null)return right;
// 否则,如果 left不为空,在左子树中有找到节点(p或q),这时候要再判断一下右子树中的情况,如果在右子树中,p和q都找不到,则 p和q一定都在左子树中,左子树中先遍历到的那个就是最近公共祖先(一个节点也可以是它自己的祖先)
else if (right==null)return left;
//否则,当 left和 right均不为空时,说明 p、q节点分别在 root异侧, 最近公共祖先即为 root
else return root;
}
}