前言:二进制、位运算符基础
1.十进制转二进制
原理:给定的数循环除以2,直到商为0或者1为止。将每一步除的结果的余数记录下来,然后反过来就得到相应的二进制了。
比如8转二进制,第一次除以2等于4(余数0),第二次除以2等于2(余数0),第三次除以2等于1(余数0),最后余数1,得到的余数依次是0 0 0 1 ,
反过来就是1000,计算机内部表示数的字节长度是固定的,比如8位,16位,32位。所以在高位补齐,java中字节码是8位的,所以高位补齐就是00001000.
写法位(8)10=(00001000)2;
代码实现:
package sourceCode.hashMap;
public class mapHashCodeTest {
public static void main(String[] args) {
String str = toBinary(8);
System.out.println(str);
}
static String toBinary(int num) {
String str = "";
while (num != 0) {
str = num % 2 + str;
num = num / 2;
}
return str;
}
}
运行结果:1000
2.二进制转十进制
计算也很简单,比如8的二进制表示位00001000,去掉补齐的高位就是1000.此时从个位开始计算2的幂(个位是0,依次往后推)乘以对应位数上的数,然后得到的值想加。
于是有了,(2的0次幂)*0+(2的1次幂)*0+(2的2次幂)*0+(2的3次幂)*1 = 8
代码实现,直接调用Integer.parseInt("",2);
System.out.println(Integer.parseInt("1000",2));
运行结果:8
3.位异或运算(^)
运算规则是:两个数转为二进制,然后从高位开始比较,如果相同则为0,不相同则为1。
比如:8^11.
8转为二进制是1000,11转为二进制是1011.从高位开始比较得到的是:0011.然后二进制转为十进制,就是Integer.parseInt("0011",2)=3;
4.位与运算符(&)
运算规则:两个数都转为二进制,然后从高位开始比较,如果两个数都为1则为1,否则为0。
比如:129&128.
129转换成二进制就是10000001,128转换成二进制就是10000000。从高位开始比较得到,得到10000000,即128.
5.位或运算符(|)
运算规则:两个数都转为二进制,然后从高位开始比较,两个数只要有一个为1则为1,否则就为0。
比如:129|128.
129转换成二进制就是10000001,128转换成二进制就是10000000。从高位开始比较得到,得到10000001,即129.
6.位非运算符(~)
运算规则:如果位为0,结果是1,如果位为1,结果是0.
比如:~37
在Java中,所有数据的表示方法都是以补码的形式表示,如果没有特殊说明,Java中的数据类型默认是int,int数据类型的长度是8位,一位是四个字节,就是32字节,32bit.
8转为二进制是100101.
补码后为: 00000000 00000000 00000000 00100101
取反为: 11111111 11111111 11111111 11011010
因为高位是1,所以原码为负数,负数的补码是其绝对值的原码取反,末尾再加1。
因此,我们可将这个二进制数的补码进行还原: 首先,末尾减1得反码:11111111 11111111 11111111 11011001 其次,将各位取反得原码:
00000000 00000000 00000000 00100110,此时二进制转原码为38
所以~37 = -38.
冒泡排序
定义一个布尔变量 hasChange
,用来标记每轮是否进行了交换。在每轮遍历开始时,将 hasChange
设置为 false。
若当轮没有发生交换,说明此时数组已经按照升序排列,hashChange
依然是为 false。此时外层循环直接退出,排序结束。
代码示例
Java
import java.util.Arrays;
public class BubbleSort {
private static void bubbleSort(int[] nums) {
boolean hasChange = true;
for (int i = 0, n = nums.length; i < n - 1 && hasChange; ++i) {
hasChange = false;
for (int j = 0; j < n - i - 1; ++j) {
if (nums[j] > nums[j + 1]) {
swap(nums, j, j + 1);
hasChange = true;
}
}
}
}
private static void swap(int[] nums, int i, int j) {
int t = nums[i];
nums[i] = nums[j];
nums[j] = t;
}
public static void main(String[] args) {
int[] nums = {1, 2, 7, 9, 5, 8};
bubbleSort(nums);
System.out.println(Arrays.toString(nums));
}
}
算法分析
空间复杂度 O(1)、时间复杂度 O(n²)。
分情况讨论:
- 给定的数组按照顺序已经排好:只需要进行
n-1
次比较,两两交换次数为 0,时间复杂度为 O(n),这是最好的情况。 - 给定的数组按照逆序排列:需要进行
n*(n-1)/2
次比较,时间复杂度为 O(n²),这是最坏的情况。 - 给定的数组杂乱无章。在这种情况下,平均时间复杂度 O(n²)。
因此,时间复杂度是 O(n²),这是一种稳定的排序算法。
稳定是指,两个相等的数,在排序过后,相对位置保持不变。
插入排序
先来看一个问题。一个有序的数组,我们往里面添加一个新的数据后,如何继续保持数据有序呢?很简单,我们只要遍历数组,找到数据应该插入的位置将其插入即可。
这是一个动态排序的过程,即动态地往有序集合中添加数据,我们可以通过这种方法保持集合中的数据一直有序。而对于一组静态数据,我们也可以借鉴上面讲的插入方法,来进行排序,于是就有了插入排序算法。
那么插入排序具体是如何借助上面的思想来实现排序的呢?
首先,我们将数组中的数据分为两个区间,已排序区间和未排序区间。初始已排序区间只有一个元素,就是数组的第一个元素。插入算法的核心思想是取未排序区间中的元素,在已排序区间中找到合适的插入位置将其插入,并保证已排序区间数据一直有序。重复这个过程,直到未排序区间中元素为空,算法结束。
与冒泡排序对比:
- 在冒泡排序中,经过每一轮的排序处理后,数组后端的数是排好序的。
- 在插入排序中,经过每一轮的排序处理后,数组前端的数是排好序的。
代码示例
Java
import java.util.Arrays;
public class InsertionSort {
private static void insertionSort(int[] nums) {
for (int i = 1, j, n = nums.length; i < n; ++i) {
int num = nums[i];
for (j = i - 1; j >=0 && nums[j] > num; --j) {
nums[j + 1] = nums[j];
}
nums[j + 1] = num;
}
}
public static void main(String[] args) {
int[] nums = {1, 2, 7, 9, 5, 8};
insertionSort(nums);
System.out.println(Arrays.toString(nums));
}
}
算法分析
空间复杂度 O(1),时间复杂度 O(n²)。
分情况讨论:
- 给定的数组按照顺序排好序:只需要进行 n-1 次比较,两两交换次数为 0,时间复杂度为 O(n),这是最好的情况。
- 给定的数组按照逆序排列:需要进行
n*(n-1)/2
次比较,时间复杂度为 O(n²),这是最坏的情况。 - 给定的数组杂乱无章:在这种情况下,平均时间复杂度是 O(n²)。
因此,时间复杂度是 O(n²),这也是一种稳定的排序算法。
归并排序
归并排序的核心思想是分治,把一个复杂问题拆分成若干个子问题来求解。
归并排序的算法思想是:把数组从中间划分为两个子数组,一直递归地把子数组划分成更小的数组,直到子数组里面只有一个元素的时候开始排序。排序的方法就是按照大小顺序合并两个元素。接着依次按照递归的顺序返回,不断合并排好序的数组,直到把整个数组排好序。
代码示例
Java
import java.util.Arrays;
public class MergeSort {
private static void merge(int[] nums, int low, int mid, int high, int[] temp) {
int i = low, j = mid + 1, k = low;
while (k <= high) {
if (i > mid) {
temp[k++] = nums[j++];
} else if (j > high) {
temp[k++] = nums[i++];
} else if (nums[i] <= nums[j]) {
temp[k++] = nums[i++];
} else {
temp[k++] = nums[j++];
}
}
System.arraycopy(tmp, low, nums, low, high - low + 1);
}
private static void mergeSort(int[] nums, int low, int high, int[] temp) {
if (low >= high) {
return;
}
int mid = low + ((high - low) >> 1);
mergeSort(nums, low, mid, temp);
mergeSort(nums, mid + 1, high, temp);
merge(nums, low, mid, high, temp);
}
private static void mergeSort(int[] nums) {
int n = nums.length;
int[] temp = new int[n];
mergeSort(nums, 0, n - 1, temp);
}
public static void main(String[] args) {
int[] nums = {1, 2, 7, 4, 5, 3};
mergeSort(nums);
System.out.println(Arrays.toString(nums));
}
}
算法分析
空间复杂度 O(n),时间复杂度 O(nlogn)。
对于规模为 n 的问题,一共要进行 log(n) 次的切分,每一层的合并复杂度都是 O(n),所以整体时间复杂度为 O(nlogn)。
由于合并 n 个元素需要分配一个大小为 n 的额外数组,所以空间复杂度为 O(n)。
这是一种稳定的排序算法。
快速排序
快速排序也采用了分治的思想:把原始的数组筛选成较小和较大的两个子数组,然后递归地排序两个子数组。
代码示例
Java
import java.util.Arrays;
public class QuickSort {
private static void quickSort(int[] nums) {
quickSort(nums, 0, nums.length - 1);
}
private static void quickSort(int[] nums, int low, int high) {
if (low >= high) {
return;
}
int[] p = partition(nums, low, high);
quickSort(nums, low, p[0] - 1);
quickSort(nums, p[0] + 1, high);
}
private static int[] partition(int[] nums, int low, int high) {
int less = low - 1, more = high;
while (low < more) {
if (nums[low] < nums[high]) {
swap(nums, ++less, low++);
} else if (nums[low] > nums[high]) {
swap(nums, --more, low);
} else {
++low;
}
}
swap(nums, more, high);
return new int[] {less + 1, more};
}
private static void swap(int[] nums, int i, int j) {
int t = nums[i];
nums[i] = nums[j];
nums[j] = t;
}
public static void main(String[] args) {
int[] nums = {1, 2, 7, 4, 5, 3};
quickSort(nums);
System.out.println(Arrays.toString(nums));
}
}
算法分析
空间复杂度 O(logn),时间复杂度 O(nlogn)。
对于规模为 n 的问题,一共要进行 log(n) 次的切分,和基准值进行 n-1 次比较,n-1 次比较的时间复杂度是 O(n),所以快速排序的时间复杂度为 O(nlogn)。
但是,如果每次在选择基准值的时候,都不幸地选择了子数组里的最大或最小值。即每次把把数组分成了两个更小长度的数组,其中一个长度为 1,另一个的长度是子数组的长度减 1。这样的算法复杂度变成 O(n²)。
和归并排序不同,快速排序在每次递归的过程中,只需要开辟 O(1) 的存储空间来完成操作来实现对数组的修改;而递归次数为 logn,所以它的整体空间复杂度完全取决于压堆栈的次数。
如何优化快速排序?
前面讲到,最坏情况下快速排序的时间复杂度是 O(n²),实际上,这种 O(n²) 时间复杂度出现的主要原因还是因为我们基准值选得不够合理。最理想的基准点是:被基准点分开的两个子数组中,数据的数量差不多。
如果很粗暴地直接选择第一个或者最后一个数据作为基准值,不考虑数据的特点,肯定会出现之前讲的那样,在某些情况下,排序的最坏情况时间复杂度是 O(n²)。
有两个比较常用的分区算法。
1. 三数取中法
我们从区间的首、尾、中间,分别取出一个数,然后对比大小,取这 3 个数的中间值作为分区点。这样每间隔某个固定的长度,取数据出来比较,将中间值作为分区点的分区算法,肯定要比单纯取某一个数据更好。但是,如果要排序的数组比较大,那“三数取中”可能就不够了,可能要“五数取中”或者“十数取中”。
2. 随机法
随机法就是每次从要排序的区间中,随机选择一个元素作为分区点。这种方法并不能保证每次分区点都选的比较好,但是从概率的角度来看,也不大可能会出现每次分区点都选的很差的情况,所以平均情况下,这样选的分区点是比较好的。时间复杂度退化为最糟糕的 O(n²) 的情况,出现的可能性不大。
选择排序
选择排序算法的实现思路有点类似插入排序,也分已排序区间和未排序区间。但是选择排序每次会从未排序区间中找到最小的元素,将其放到已排序区间的末尾。
代码示例
Java
import java.util.Arrays;
public class SelectionSort {
private static void selectionSort(int[] nums) {
for (int i = 0, n = nums.length; i < n - 1; ++i) {
int minIndex = i;
for (int j = i; j < n; ++j) {
if (nums[j] < nums[minIndex]) {
minIndex = j;
}
}
swap(nums, minIndex, i);
}
}
private static void swap(int[] nums, int i, int j) {
int t = nums[i];
nums[i] = nums[j];
nums[j] = t;
}
public static void main(String[] args) {
int[] nums = {1, 2, 7, 9, 5, 8};
selectionSort(nums);
System.out.println(Arrays.toString(nums));
}
}
算法分析
空间复杂度 O(1),时间复杂度 O(n²)。
那选择排序是稳定的排序算法吗?
答案是否定的,选择排序是一种不稳定的排序算法。选择排序每次都要找剩余未排序元素中的最小值,并和前面的元素交换位置,这样破坏了稳定性。
比如 5,8,5,2,9 这样一组数据,使用选择排序算法来排序的话,第一次找到最小元素 2,与第一个 5 交换位置,那第一个 5 和中间的 5 顺序就变了,所以就不稳定了。正是因此,相对于冒泡排序和插入排序,选择排序就稍微逊色了。
二分查找
二分查找是一种非常高效的查找算法,高效到什么程度呢?我们来分析一下它的时间复杂度。
假设数据大小是 n,每次查找后数据都会缩小为原来的一半,也就是会除以 2。最坏情况下,直到查找区间被缩小为空,才停止。
被查找区间的大小变化为:
n, n/2, n/4, n/8, ..., n/(2^k)
可以看出来,这是一个等比数列。其中 n/(2^k)=1
时,k 的值就是总共缩小的次数。而每一次缩小操作只涉及两个数据的大小比较,所以,经过了 k 次区间缩小操作,时间复杂度就是 O(k)。通过 n/(2^k)=1
,我们可以求得 k=log2n
,所以时间复杂度就是 O(logn)。
代码示例
注意容易出错的 3 个地方。
- 循环退出条件是
low <= high
,而不是low < high
; - mid 的取值,可以是
mid = (low + high) / 2
,但是如果 low 和 high 比较大的话,low + high
可能会溢出,所以这里写为mid = low + ((high - low) >> 1)
; - low 和 high 的更新分别为
low = mid + 1
、high = mid - 1
。
Java
非递归实现:
public class BinarySearch {
private static int search(int[] nums, int low, int high, int val) {
while (low <= high) {
int mid = low + ((high -low) >> 1);
if (nums[mid] == val) {
return mid;
} else if (nums[mid] < val) {
low = mid + 1;
} else {
high = mid - 1;
}
}
return -1;
}
/**
* 二分查找(非递归)
*
* @param nums 有序数组
* @param val 要查找的值
* @return 要查找的值在数组中的索引位置
*/
private static int search(int[] nums, int val) {
return search(nums, 0, nums.length - 1, val);
}
public static void main(String[] args) {
int[] nums = {1, 2, 5, 7, 8, 9};
// 非递归查找
int r1 = search(nums, 7);
System.out.println(r1);
}
}
递归实现:
public class BinarySearch {
private static int searchRecursive(int[] nums, int low, int high, int val) {
while (low <= high) {
int mid = low + ((high - low) >> 1);
if (nums[mid] == val) {
return mid;
} else if (nums[mid] < val) {
return searchRecursive(nums, mid + 1, high, val);
} else {
return searchRecursive(nums, low, mid - 1, val);
}
}
return -1;
}
/**
* 二分查找(递归)
*
* @param nums 有序数组
* @param val 要查找的值
* @return 要查找的值在数组中的索引位置
*/
private static int searchRecursive(int[] nums, int val) {
return searchRecursive(nums, 0, nums.length - 1, val);
}
public static void main(String[] args) {
int[] nums = {1, 2, 5, 7, 8, 9};
// 递归查找
int r2 = searchRecursive(nums, 7);
System.out.println(r2);
}
}
二分查找 II
前面讲的二分查找算法,是最为简单的一种,在不存在重复元素的有序数组中,查找值等于给定值的元素。
接下来,我们来看看二分查找算法四种常见的变形问题,分别是:
- 查找第一个值等于给定值的元素
- 查找最后一个值等于给定值的元素
- 查找第一个大于等于给定值的元素
- 查找最后一个小于等于给定值的元素
1. 查找第一个值等于给定值的元素
Java
public class BinarySearch {
public static int search(int[] nums, int val) {
int n = nums.length;
int low = 0, high = n - 1;
while (low <= high) {
int mid = low + ((high - low) >> 1);
if (nums[mid] < val) {
low = mid + 1;
} else if (nums[mid] > val) {
high = mid - 1;
} else {
// 如果nums[mid]是第一个元素,或者nums[mid-1]不等于val
// 说明nums[mid]就是第一个值为给定值的元素
if (mid == 0 || nums[mid - 1] != val) {
return mid;
}
high = mid - 1;
}
}
return -1;
}}
2. 查找最后一个值等于给定值的元素
Java
public class Main {
public int search(int[] nums, int val) {
int n = nums.length;
int low = 0, high = n - 1;
while (low <= high) {
int mid = low + ((high - low) >> 1);
if (nums[mid] < val) {
low = mid + 1;
} else if (nums[mid] > val) {
high = mid - 1;
} else {
// 如果nums[mid]是最后一个元素,或者nums[mid+1]不等于val
// 说明nums[mid]就是最后一个值为给定值的元素
if (mid == n - 1 || nums[mid + 1] != val) {
return mid;
}
low = mid + 1;
}
}
return -1;
}}
3. 查找第一个大于等于给定值的元素
Java
public class Main {
public static int search(int[] nums, int val) {
int low = 0, high = nums.length - 1;
while (low <= high) {
int mid = low + ((high - low) >> 1);
if (nums[mid] < val) {
low = mid + 1;
} else {
// 如果nums[mid]是第一个元素,或者nums[mid-1]小于val
// 说明nums[mid]就是第一个大于等于给定值的元素
if (mid == 0 || nums[mid - 1] < val) {
return mid;
}
high = mid - 1;
}
}
return -1;
}}
4. 查找最后一个小于等于给定值的元素
Java
public class Main {
public static int search(int[] nums, int val) {
int n = nums.length;
int low = 0, high = n - 1;
while (low <= high) {
int mid = low + ((high - low) >> 1);
if (nums[mid] > val) {
high = mid - 1;
} else {
// 如果nums[mid]是最后一个元素,或者nums[mid+1]大于val
// 说明nums[mid]就是最后一个小于等于给定值的元素
if (mid == n - 1 || nums[mid + 1] > val) {
return mid;
}
low = mid + 1;
}
}
return -1;
}}
二叉树深度优先dfs与广度优先bfs算法
public class BinaryTree {
// 二叉树节点
public static class BinaryTreeNode {
int value;
BinaryTreeNode left;
BinaryTreeNode right;
public BinaryTreeNode(int value) {
this.value = value;
}
}
// 访问树的节点
public static void visit(BinaryTreeNode node) {
System.out.println(node.value);
}
/**
* 递归实现二叉树的先序遍历
*/
public static void preOrder(BinaryTreeNode node) {
if (node != null) {
visit(node);
preOrder(node.left);
preOrder(node.right);
}
}
/**
* 递归实现二叉树的中序遍历
*/
public static void inOrder(BinaryTreeNode node) {
if (node != null) {
inOrder(node.left);
visit(node);
inOrder(node.right);
}
}
/**
* 递归实现二叉树的后序遍历
*/
public static void postOrder(BinaryTreeNode node) {
if (node != null) {
postOrder(node.left);
postOrder(node.right);
visit(node);
}
}
/**
* 非递归实现二叉树的先序遍历
*/
public static void iterativePreorder(BinaryTreeNode node) {
Stack<BinaryTreeNode> stack = new Stack<>();
if (node != null) {
stack.push(node);
while (!stack.empty()) {
node = stack.pop();
// 先访问节点
visit(node);
// 把右子结点压入栈
if (node.right != null) {
stack.push(node.right);
}
// 把左子结点压入栈
if (node.left != null) {
stack.push(node.left);
}
}
}
}
/**
* 非递归实现二叉树的中序遍历
*/
public static void iterativeInOrder(BinaryTreeNode root) {
Stack<BinaryTreeNode> stack = new Stack<>();
BinaryTreeNode node = root;
while (node != null || stack.size() > 0) {
// 把当前节点的全部左侧子结点压入栈
while (node != null) {
stack.push(node);
node = node.left;
}
// 访问节点,处理该节点的右子树
if (stack.size() > 0) {
node = stack.pop();
visit(node);
node = node.right;
}
}
}
/**
* 非递归使用单栈实现二叉树后序遍历
*/
public static void iterativePostOrder(BinaryTreeNode root) {
Stack<BinaryTreeNode> stack = new Stack<>();
BinaryTreeNode node = root;
// 访问根节点时判断其右子树是够被访问过
BinaryTreeNode preNode = null;
while (node != null || stack.size() > 0) {
// 把当前节点的左侧节点所有入栈
while (node != null) {
stack.push(node);
node = node.left;
}
if (stack.size() > 0) {
BinaryTreeNode temp = stack.peek().right;
// 一个根节点被访问的前提是:无右子树或右子树已被访问过
if (temp == null || temp == preNode) {
node = stack.pop();
visit(node);
preNode = node;// 记录刚被访问过的节点
node = null;
} else {
// 处理右子树
node = temp;
}
}
}
}
/**
* 非递归使用双栈实现二叉树后序遍历
*/
public static void iterativePostOrderByTwoStacks(BinaryTreeNode root) {
Stack<BinaryTreeNode> stack = new Stack<>();
Stack<BinaryTreeNode> temp = new Stack<>();
BinaryTreeNode node = root;
while (node != null || stack.size() > 0) {
// 把当前节点和其右侧子结点推入栈
while (node != null) {
stack.push(node);
temp.push(node);
node = node.right;
}
// 处理栈顶节点的左子树
if (stack.size() > 0) {
node = stack.pop();
node = node.left;
}
}
while (temp.size() > 0) {
node = temp.pop();
visit(node);
}
}
/**
* 二叉树广度优先遍历——层序遍历
*/
public static void layerTraversal(BinaryTreeNode root) {
Queue<BinaryTreeNode> queue = new LinkedList<>();
if (root != null) {
queue.add(root);
while (!queue.isEmpty()) {
BinaryTreeNode currentNode = queue.poll();
visit(currentNode);
if (currentNode.left != null) {
queue.add(currentNode.left);
}
if (currentNode.right != null) {
queue.add(currentNode.right);
}
}
}
}
}
1. 两数之和
题目描述
给定一个整数数组 nums
和一个目标值 target
,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。
示例:
给定 nums = [2, 7, 11, 15], target = 9 因为 nums[0] + nums[1] = 2 + 7 = 9 所以返回 [0, 1]
解法
用哈希表(字典)存放数组值以及对应的下标。
遍历数组,当发现 target - nums[i]
在哈希表中,说明找到了目标值。
Java
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0, n = nums.length; i < n; ++i) {
int num = target - nums[i];
if (map.containsKey(num)) {
return new int[]{map.get(num), i};
}
map.put(nums[i], i);
}
return null;
}
}
2. 两数相加
题目描述
给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。
如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。
您可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例:
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4) 输出:7 -> 0 -> 8 原因:342 + 465 = 807
解法
Java
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
int carry = 0;
ListNode dummy = new ListNode(-1);
ListNode cur = dummy;
while (l1 != null || l2 != null || carry != 0) {
int t = (l1 == null ? 0 : l1.val) + (l2 == null ? 0 : l2.val) + carry;
carry = t / 10;
cur.next = new ListNode(t % 10);
cur = cur.next;
l1 = l1 == null ? null : l1.next;
l2 = l2 == null ? null : l2.next;
}
return dummy.next;
}
}
3. 无重复字符的最长子串
题目描述
给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其
长度为 3。
示例 2:
输入: "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b"
,所以其长度为 1。
示例 3:
输入: "pwwkew" 输出: 3 解释: 因为无重复字符的最长子串是"wke"
,所以其长度为 3。 请注意,你的答案必须是 子串 的长度,"pwke"
是一个子序列,不是子串。
解法
Java
class Solution {
public int lengthOfLongestSubstring(String s) {
Map<Character, Integer> map = new HashMap<>();
int max = 0;
for (int fast = 0, slow = 0; fast < s.length(); fast ++) {
if (map.containsKey(s.charAt(fast))) {
int target = map.get(s.charAt(fast)) + 1;
slow = target < slow ? slow : target;
}
map.put(s.charAt(fast), fast);
max = Math.max(max, fast - slow + 1);
}
return max;
}
}
4. 寻找两个有序数组的中位数
题目描述
给定两个大小为 m 和 n 的有序数组 nums1
和 nums2
。
请你找出这两个有序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。
你可以假设 nums1
和 nums2
不会同时为空。
示例 1:
nums1 = [1, 3] nums2 = [2] 则中位数是 2.0
示例 2:
nums1 = [1, 2] nums2 = [3, 4] 则中位数是 (2 + 3)/2 = 2.5
解法
Java
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int len1 = nums1.length;
int len2 = nums2.length;
if (len1 > len2) {
int[] tmp = nums1;
nums1 = nums2;
nums2 = tmp;
int t = len1;
len1 = len2;
len2 = t;
}
int min = 0;
int max = len1;
int m = (len1 + len2 + 1) / 2;
while (min <= max) {
int i = (min + max) / 2;
int j = m - i;
if (i > min && nums1[i - 1] > nums2[j]) {
--max;
} else if (i < max && nums2[j - 1] > nums1[i]) {
++min;
} else {
int maxLeft = i == 0 ? nums2[j - 1] : j == 0 ? nums1[i - 1] : Math.max(nums1[i - 1], nums2[j - 1]);
if (((len1 + len2) & 1) == 1) {
return maxLeft;
}
int minRight = i == len1 ? nums2[j] : j == len2 ? nums1[i] : Math.min(nums2[j], nums1[i]);
return (maxLeft + minRight) / 2.0;
}
}
return 0;
}
}
5. 最长回文子串
题目描述
给定一个字符串 s
,找到 s
中最长的回文子串。你可以假设 s
的最大长度为 1000。
示例 1:
输入: "babad" 输出: "bab" 注意: "aba" 也是一个有效答案。
示例 2:
输入: "cbbd" 输出: "bb"
解法
Java
class Solution {
public String longestPalindrome(String s) {
if (s == null || s.length() < 1) {
return "";
}
String str = "";
char[] chars = s.toCharArray();
int len = chars.length;
boolean[][] res = new boolean[len][len];
int start = 0;
int max = 1;
for (int i = 0; i < len; ++i) {
for (int j = 0; j <= i; ++j) {
res[j][i] = i - j < 2 ? chars[j] == chars[i] : res[j + 1][i - 1] && chars[j] == chars[i];
if (res[j][i] && max < i - j + 1) {
max = i - j + 1;
start = j;
}
}
}
return s.substring(start, start + max);
}
}
6. Z 字形变换
题目描述
将一个给定字符串根据给定的行数,以从上往下、从左到右进行 Z 字形排列。
比如输入字符串为 "LEETCODEISHIRING"
行数为 3 时,排列如下:
L C I R E T O E S I I G E D H N
之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:"LCIRETOESIIGEDHN"
。
请你实现这个将字符串进行指定行数变换的函数:
string convert(string s, int numRows);
示例 1:
输入: s = "LEETCODEISHIRING", numRows = 3 输出: "LCIRETOESIIGEDHN"
示例 2:
输入: s = "LEETCODEISHIRING", numRows = 4 输出: "LDREOEIIECIHNTSG" 解释: L D R E O E I I E C I H N T S G
解法
Java
class Solution {
public String convert(String s, int numRows) {
if (numRows == 1) return s;
StringBuilder result = new StringBuilder();
int group = 2 * numRows - 2;
for (int i = 1; i <= numRows; i++) {
int interval = 2 * numRows - 2 * i;
if (i == numRows) interval = 2 * numRows - 2;
int index = i;
while (index <= s.length()) {
result.append(s.charAt(index - 1));
index += interval;
interval = group - interval;
if (interval == 0) interval = group;
}
}
return result.toString();
}
}
7. 整数反转
题目描述
给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转。
示例 1:
输入: 123 输出: 321
示例 2:
输入: -123 输出: -321
示例 3:
输入: 120 输出: 21
注意:
假设我们的环境只能存储得下 32 位的有符号整数,则其数值范围为 [−231, 231 − 1]。请根据这个假设,如果反转后整数溢出那么就返回 0。
解法
Java
/*
class Solution {
public int reverse(int x) {
if (x == 0) {
return x;
}
long tmp = x;
boolean isPositive = true;
if (tmp < 0) {
isPositive = false;
tmp = -tmp;
}
long val = Long.parseLong(new StringBuilder(String.valueOf(tmp)).reverse().toString());
return isPositive ? (val > Integer.MAX_VALUE ? 0 : (int) val) : (-val < Integer.MIN_VALUE ? 0 : (int) (-val));
}
}
*/
class Solution {
public int reverse(int x) {
long res = 0;
// 考虑负数情况,所以这里条件为: x != 0
while (x != 0) {
res = res * 10 + (x % 10);
x /= 10;
}
return (res < Integer.MIN_VALUE || res > Integer.MAX_VALUE)
? 0
: (int) res;
}
}
8. 字符串转换整数 (atoi)
题目描述
请你来实现一个 atoi
函数,使其能将字符串转换成整数。
首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。
当我们寻找到的第一个非空字符为正或者负号时,则将该符号与之后面尽可能多的连续数字组合起来,作为该整数的正负号;假如第一个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成整数。
该字符串除了有效的整数部分之后也可能会存在多余的字符,这些字符可以被忽略,它们对于函数不应该造成影响。
注意:假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换。
在任何情况下,若函数不能进行有效的转换时,请返回 0。
说明:
假设我们的环境只能存储 32 位大小的有符号整数,那么其数值范围为 [−231, 231 − 1]。如果数值超过这个范围,请返回 INT_MAX (231 − 1) 或 INT_MIN (−231) 。
示例 1:
输入: "42" 输出: 42
示例 2:
输入: " -42" 输出: -42 解释: 第一个非空白字符为 '-', 它是一个负号。 我们尽可能将负号与后面所有连续出现的数字组合起来,最后得到 -42 。
示例 3:
输入: "4193 with words" 输出: 4193 解释: 转换截止于数字 '3' ,因为它的下一个字符不为数字。
示例 4:
输入: "words and 987" 输出: 0 解释: 第一个非空字符是 'w', 但它不是数字或正、负号。 因此无法执行有效的转换。
示例 5:
输入: "-91283472332" 输出: -2147483648 解释: 数字 "-91283472332" 超过 32 位有符号整数范围。 因此返回 INT_MIN (−231) 。
解法
遍历字符串,注意做溢出处理。
同面试题 67. 把字符串转换成整数。
Java
class Solution {
public int myAtoi(String s) {
if (s == null) return 0;
int n = s.length();
if (n == 0) return 0;
int i = 0;
while (s.charAt(i) == ' ') {
// 仅包含空格
if (++i == n) return 0;
}
int sign = 1;
if (s.charAt(i) == '-') sign = -1;
if (s.charAt(i) == '-' || s.charAt(i) == '+') ++i;
int res = 0, flag = Integer.MAX_VALUE / 10;
for (; i < n; ++i) {
// 非数字,跳出循环体
if (s.charAt(i) < '0' || s.charAt(i) > '9') break;
// 溢出判断
if (res > flag || (res == flag && s.charAt(i) > '7')) return sign > 0 ? Integer.MAX_VALUE : Integer.MIN_VALUE;
res = res * 10 + (s.charAt(i) - '0');
}
return sign * res;
}
}
9. 回文数
题目描述
判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。
示例 1:
输入: 121 输出: true
示例 2:
输入: -121 输出: false 解释: 从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。
示例 3:
输入: 10 输出: false 解释: 从右向左读, 为 01 。因此它不是一个回文数。
进阶:
你能不将整数转为字符串来解决这个问题吗?
解法
Java
class Solution {
public boolean isPalindrome(int x) {
if (x < 0) return false;
int y = 0, t = x;
while (t != 0) {
y = y * 10 + t % 10;
t /= 10;
}
return x == y;
}
}
10. 正则表达式匹配
题目描述
给你一个字符串 s
和一个字符规律 p
,请你来实现一个支持 '.'
和 '*'
的正则表达式匹配。
'.' 匹配任意单个字符 '*' 匹配零个或多个前面的那一个元素
所谓匹配,是要涵盖 整个 字符串 s
的,而不是部分字符串。
说明:
s
可能为空,且只包含从a-z
的小写字母。p
可能为空,且只包含从a-z
的小写字母,以及字符.
和*
。
示例 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
解法
Java
class Solution {
public boolean isMatch(String s, String p) {
boolean[] match = new boolean[s.length() + 1];
match[s.length()] = true;
for (int i = p.length() - 1; i >= 0; i--) {
if (p.charAt(i) == '*') {
for (int j = s.length() - 1; j >= 0; j--) {
match[j] = match[j] || (match[j + 1] && (p.charAt(i - 1) == '.' ||
(p.charAt(i - 1) == s.charAt(j))));
}
i--;
} else {
for (int j = 0; j < s.length(); j++) {
match[j] = match[j + 1] && (p.charAt(i) == '.' || (p.charAt(i) == s.charAt(j)));
}
match[s.length()] = false;
}
}
return match[0];
}
}
11. 盛最多水的容器
题目描述
给你 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
说明:你不能倾斜容器,且 n 的值至少为 2。
图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
示例:
输入:[1,8,6,2,5,4,8,3,7] 输出:49
解法
Java
class Solution {
public int maxArea(int[] height) {
int start = 0, end = height.length - 1, maxArea = 0;
while (start < end) {
int hs = height[start];
int he = height[end];
int l = end - start;
if (hs > he) {
maxArea = Math.max(he * l, maxArea);
end--;
} else {
maxArea = Math.max(hs * l, maxArea);
start++;
}
}
return maxArea;
}
}
12. 整数转罗马数字
题目描述
罗马数字包含以下七种字符: I
, V
, X
, L
,C
,D
和 M
。
字符 数值 I 1 V 5 X 10 L 50 C 100 D 500 M 1000
例如, 罗马数字 2 写做 II
,即为两个并列的 1。12 写做 XII
,即为 X
+ II
。 27 写做 XXVII
, 即为 XX
+ V
+ II
。
通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII
,而是 IV
。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX
。这个特殊的规则只适用于以下六种情况:
I
可以放在V
(5) 和X
(10) 的左边,来表示 4 和 9。X
可以放在L
(50) 和C
(100) 的左边,来表示 40 和 90。C
可以放在D
(500) 和M
(1000) 的左边,来表示 400 和 900。
给定一个整数,将其转为罗马数字。输入确保在 1 到 3999 的范围内。
示例 1:
输入: 3 输出: "III"
示例 2:
输入: 4 输出: "IV"
示例 3:
输入: 9 输出: "IX"
示例 4:
输入: 58 输出: "LVIII" 解释: L = 50, V = 5, III = 3.
示例 5:
输入: 1994 输出: "MCMXCIV" 解释: M = 1000, CM = 900, XC = 90, IV = 4.
解法
Java
class Solution {
private final String[] M = {"", "M", "MM", "MMM"};
private final String[] C = {"", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"};
private final String[] X = {"", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"};
private final String[] I = {"", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"};
public String intToRoman(int num) {
return M[num / 1000] + C[(num % 1000) / 100] + X[(num % 100) / 10] + I[num % 10];
}
}
13. 罗马数字转整数
题目描述
罗马数字包含以下七种字符: I
, V
, X
, L
,C
,D
和 M
。
字符 数值 I 1 V 5 X 10 L 50 C 100 D 500 M 1000
例如, 罗马数字 2 写做 II
,即为两个并列的 1。12 写做 XII
,即为 X
+ II
。 27 写做 XXVII
, 即为 XX
+ V
+ II
。
通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII
,而是 IV
。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX
。这个特殊的规则只适用于以下六种情况:
I
可以放在V
(5) 和X
(10) 的左边,来表示 4 和 9。X
可以放在L
(50) 和C
(100) 的左边,来表示 40 和 90。C
可以放在D
(500) 和M
(1000) 的左边,来表示 400 和 900。
给定一个罗马数字,将其转换成整数。输入确保在 1 到 3999 的范围内。
示例 1:
输入: "III" 输出: 3
示例 2:
输入: "IV" 输出: 4
示例 3:
输入: "IX" 输出: 9
示例 4:
输入: "LVIII" 输出: 58 解释: L = 50, V= 5, III = 3.
示例 5:
输入: "MCMXCIV" 输出: 1994 解释: M = 1000, CM = 900, XC = 90, IV = 4.
解法
Java
class Solution {
public int romanToInt(String s) {
if (s == null || s.isEmpty()) {
return 0;
}
String str = "IVXLCDM";
int[] num = {1, 5, 10, 50, 100, 500, 1000};
int ans = 0;
int i = 0;
int pre = 999, cur = 0;
while (i < s.length()) {
cur = str.indexOf(s.charAt(i++));
if (pre < cur) {
ans = ans + num[cur] - 2 * num[pre];
} else {
ans += num[cur];
}
pre = cur;
}
return ans;
}
}
14. 最长公共前缀
题目描述
编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 ""
。
示例 1:
输入: ["flower","flow","flight"] 输出: "fl"
示例 2:
输入: ["dog","racecar","car"] 输出: "" 解释: 输入不存在公共前缀。
说明:
所有输入只包含小写字母 a-z
。
解法
Java
class Solution {
public String longestCommonPrefix(String[] strs) {
if (strs == null || strs.length == 0) {
return "";
}
if (strs.length == 1) {
return strs[0];
}
char[] chars = strs[0].toCharArray();
int i = 0;
boolean flag = true;
for (; i < chars.length; ++i) {
char ch = chars[i];
for (int j = 1; j < strs.length; ++j) {
if (strs[j].length() <= i) {
flag = false;
break;
}
if (strs[j].charAt(i) != ch) {
flag = false;
break;
}
}
if (!flag) {
break;
}
}
return strs[0].substring(0, i);
}
}
15. 三数之和
题目描述
给你一个包含 n 个整数的数组 nums
,判断 nums
中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例:
给定数组 nums = [-1, 0, 1, 2, -1, -4], 满足要求的三元组集合为: [ [-1, 0, 1], [-1, -1, 2] ]
解法
“排序 + 双指针”实现。
Java
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
int n;
if (nums == null || (n = nums.length) < 3) {
return Collections.emptyList();
}
Arrays.sort(nums);
List<List<Integer>> res = new ArrayList<>();
for (int i = 0; i < n - 2; ++i) {
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
int p = i + 1, q = n - 1;
while (p < q) {
if (p > i + 1 && nums[p] == nums[p - 1]) {
++p;
continue;
}
if (q < n - 1 && nums[q] == nums[q + 1]) {
--q;
continue;
}
if (nums[p] + nums[q] + nums[i] < 0) {
++p;
} else if (nums[p] + nums[q] + nums[i] > 0) {
--q;
} else {
res.add(Arrays.asList(nums[p], nums[q], nums[i]));
++p;
--q;
}
}
}
return res;
}
}
17. 电话号码的字母组合
题目描述
给定一个仅包含数字 2-9
的字符串,返回所有它能表示的字母组合。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
示例:
输入:"23" 输出:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].
说明:
尽管上面的答案是按字典序排列的,但是你可以任意选择答案输出的顺序。
解法
Java
class Solution {
public List<String> letterCombinations(String digits) {
char[] cs = digits.toCharArray();
List<String> result = new ArrayList<>();
for (char a : cs) {
char[] charArray;
switch (a) {
case '2': charArray = new char[]{'a','b','c'}; break;
case '3': charArray = new char[]{'d','e','f'}; break;
case '4': charArray = new char[]{'g','h','i'}; break;
case '5': charArray = new char[]{'j','k','l'}; break;
case '6': charArray = new char[]{'m','n','o'}; break;
case '7': charArray = new char[]{'p','q','r','s'}; break;
case '8': charArray = new char[]{'t','u','v'}; break;
case '9': charArray = new char[]{'w','x','y','z'}; break;
default: return null;
}
if (result.size() == 0) {
for (char aCharArray : charArray) result.add(String.valueOf(aCharArray));
} else {
List<String> cache = new ArrayList<>();
for (String string : result) {
for (char aCharArray : charArray) cache.add(string + aCharArray);
}
result = cache;
}
}
return result;
}
}
18. 四数之和
题目描述
给定一个包含 n 个整数的数组 nums
和一个目标值 target
,判断 nums
中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target
相等?找出所有满足条件且不重复的四元组。
注意:
答案中不可以包含重复的四元组。
示例:
给定数组 nums = [1, 0, -1, 0, -2, 2],和 target = 0。 满足要求的四元组集合为: [ [-1, 0, 0, 1], [-2, -1, 1, 2], [-2, 0, 0, 2] ]
解法
“排序 + 双指针”实现。
Java
class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
int n;
if (nums == null || (n = (nums.length)) < 4) {
return Collections.emptyList();
}
Arrays.sort(nums);
List<List<Integer>> res = new ArrayList<>();
for (int i = 0; i < n - 3; ++i) {
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
for (int j = i + 1; j < n - 2; ++j) {
if (j > i + 1 && nums[j] == nums[j - 1]) {
continue;
}
int p = j + 1, q = n - 1;
while (p < q) {
if (p > j + 1 && nums[p] == nums[p - 1]) {
++p;
continue;
}
if (q < n - 1 && nums[q] == nums[q + 1]) {
--q;
continue;
}
int t = nums[i] + nums[j] + nums[p] + nums[q];
if (t == target) {
res.add(Arrays.asList(nums[i], nums[j], nums[p], nums[q]));
++p;
--q;
} else if (t < target) {
++p;
} else {
--q;
}
}
}
}
return res;
}
}
19. 删除链表的倒数第 N 个节点
题目描述
给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。
示例:
给定一个链表: 1->2->3->4->5, 和 n = 2. 当删除了倒数第二个节点后,链表变为 1->2->3->5.
说明:
给定的 n 保证是有效的。
进阶:
你能尝试使用一趟扫描实现吗?
解法
Java
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(0, head);
ListNode p = dummy, q = dummy;
while (n-- > 0) {
p = p.next;
}
while (p.next != null) {
p = p.next;
q = q.next;
}
q.next = q.next.next;
return dummy.next;
}
}
20. 有效的括号
题目描述
给定一个只包括 '('
,')'
,'{'
,'}'
,'['
,']'
的字符串,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。
示例 1:
输入: "()" 输出: true
示例 2:
输入: "()[]{}" 输出: true
示例 3:
输入: "(]" 输出: false
示例 4:
输入: "([)]" 输出: false
示例 5:
输入: "{[]}" 输出: true
解法
栈实现。
遍历括号字符串,遇到左括号时,将左括号压入栈中;遇到右括号时,弹出栈顶元素(栈若为空,直接返回 false),判断是否是相同类型的括号。若不匹配,直接返回 false。
遍历结束,栈若为空,说明括号字符串有效。
Java
class Solution {
public boolean isValid(String s) {
char[] chars = s.toCharArray();
Deque<Character> q = new ArrayDeque<>();
for (char ch : chars) {
boolean left = ch == '(' || ch == '[' || ch == '{';
if (left) q.push(ch);
else if (q.isEmpty() || !match(q.pop(), ch)) return false;
}
return q.isEmpty();
}
private boolean match(char l, char r) {
return (l == '(' && r == ')') || (l == '{' && r == '}') || (l == '[' && r == ']');
}
}
21. 合并两个有序链表
题目描述
将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例:
输入:1->2->4, 1->3->4 输出:1->1->2->3->4->4
解法
Java
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode dummy = new ListNode(0);
ListNode cur = dummy;
while (l1 != null && l2 != null) {
if (l1.val <= l2.val) {
cur.next = l1;
l1 = l1.next;
} else {
cur.next = l2;
l2 = l2.next;
}
cur = cur.next;
}
cur.next = l1 == null ? l2 : l1;
return dummy.next;
}
}
22. 括号生成
题目描述
给出 n 代表生成括号的对数,请你写出一个函数,使其能够生成所有可能的并且有效的括号组合。
例如,给出 n = 3,生成结果为:
[ "((()))", "(()())", "(())()", "()(())", "()()()" ]
解法
Java
class Solution {
public List<String> generateParenthesis(int n) {
List<String> res = new ArrayList<>();
dfs(res, "", 0, 0, n);
return res;
}
private void dfs(List<String> res, String ans, int l, int r, int length) {
if (ans.length() == length * 2) {
res.add(ans);
return;
}
if (l < length) {
dfs(res, ans + "(", l + 1, r, length);
}
if (r < l) {
dfs(res, ans + ")", l, r + 1, length);
}
}
}
23. 合并 K 个排序链表
题目描述
合并 k 个排序链表,返回合并后的排序链表。请分析和描述算法的复杂度。
示例:
输入: [ 1->4->5, 1->3->4, 2->6 ] 输出: 1->1->2->3->4->4->5->6
解法
合并前后两个链表,结果放在后一个链表位置上,依次循环下去。
Java
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
int n;
if (lists == null || (n = lists.length) == 0) {
return null;
}
for (int i = 1; i < n; ++i) {
lists[i] = mergeTwoLists(lists[i - 1], lists[i]);
}
return lists[n - 1];
}
private ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode dummy = new ListNode(0);
ListNode cur = dummy;
while (l1 != null && l2 != null) {
if (l1.val <= l2.val) {
cur.next = l1;
l1 = l1.next;
} else {
cur.next = l2;
l2 = l2.next;
}
cur = cur.next;
}
cur.next = l1 == null ? l2 : l1;
return dummy.next;
}
}
24. 两两交换链表中的节点
题目描述
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
示例:
给定1->2->3->4
, 你应该返回2->1->4->3
.
解法
Java
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode swapPairs(ListNode head) {
ListNode dummy = new ListNode(0, head);
ListNode pre = dummy, cur = head;
while (cur != null && cur.next != null) {
ListNode t = cur.next;
cur.next = t.next;
t.next = cur;
pre.next = t;
pre = cur;
cur = pre.next;
}
return dummy.next;
}
}
25. K 个一组翻转链表
题目描述
给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。
k 是一个正整数,它的值小于或等于链表的长度。
如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。
示例:
给你这个链表:1->2->3->4->5
当 k = 2 时,应当返回: 2->1->4->3->5
当 k = 3 时,应当返回: 3->2->1->4->5
说明:
- 你的算法只能使用常数的额外空间。
- 你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。
解法
Java
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode reverseKGroup(ListNode head, int k) {
if(head == null || k < 2) {
return head;
}
int num = 0;
ListNode pNode = head;
ListNode lastNode = new ListNode(0);
ListNode reNode = lastNode;
lastNode.next = head;
while (pNode != null) {
num++;
if(num >= k) {
num = 0;
ListNode tempNode = pNode.next;
reverse(lastNode.next, k);
// k 个节点的尾节点指向下一组的头节点
lastNode.next.next = tempNode;
// 上一组的尾节点指向当前 k 个节点的头节点
tempNode = lastNode.next;
lastNode.next = pNode;
lastNode = tempNode;
pNode = lastNode.next;
}
else {
pNode = pNode.next;
}
}
return reNode.next;
}
private ListNode reverse(ListNode node, int i) {
if(i <= 1 || node.next == null) {
return node;
}
ListNode lastNode = reverse(node.next, i - 1);
lastNode.next = node;
return node;
}
}
26. 删除排序数组中的重复项
题目描述
给定一个排序数组,你需要在 原地 删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
示例 1:
给定数组 nums = [1,1,2], 函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为1
,2
。 你不需要考虑数组中超出新长度后面的元素。
示例 2:
给定 nums = [0,0,1,1,1,2,2,3,3,4], 函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为0
,1
,2
,3
,4
。 你不需要考虑数组中超出新长度后面的元素。
说明:
为什么返回数值是整数,但输出的答案是数组呢?
请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。
你可以想象内部操作如下:
// nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝 int len = removeDuplicates(nums); // 在函数里修改输入数组对于调用者是可见的。 // 根据你的函数返回的长度, 它会打印出数组中该长度范围内的所有元素。 for (int i = 0; i < len; i++) { print(nums[i]); }
解法
Java
class Solution {
public int removeDuplicates(int[] nums) {
int cnt = 0, n = nums.length;
for (int i = 1; i < n; ++i) {
if (nums[i] == nums[i - 1]) ++cnt;
else nums[i - cnt] = nums[i];
}
return n - cnt;
}
}
27. 移除元素
题目描述
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
示例 1:
给定 nums = [3,2,2,3], val = 3, 函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。 你不需要考虑数组中超出新长度后面的元素。
示例 2:
给定 nums = [0,1,2,2,3,0,4,2], val = 2, 函数应该返回新的长度5
, 并且 nums 中的前五个元素为0
,1
,3
,0
, 4。 注意这五个元素可为任意顺序。 你不需要考虑数组中超出新长度后面的元素。
说明:
为什么返回数值是整数,但输出的答案是数组呢?
请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。
你可以想象内部操作如下:
// nums 是以“引用”方式传递的。也就是说,不对实参作任何拷贝 int len = removeElement(nums, val); // 在函数里修改输入数组对于调用者是可见的。 // 根据你的函数返回的长度, 它会打印出数组中 该长度范围内 的所有元素。 for (int i = 0; i < len; i++) { print(nums[i]); }
解法
Java
class Solution {
public int removeElement(int[] nums, int val) {
int cnt = 0, n = nums.length;
for (int i = 0; i < n; ++i) {
if (nums[i] == val) {
++cnt;
} else {
nums[i - cnt] = nums[i];
}
}
return n - cnt;
}
}
28. 实现 strStr()
题目描述
实现 strStr() 函数。
给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回 -1。
示例 1:
输入: haystack = "hello", needle = "ll" 输出: 2
示例 2:
输入: haystack = "aaaaa", needle = "bba" 输出: -1
说明:
当 needle
是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。
对于本题而言,当 needle
是空字符串时我们应当返回 0 。这与C语言的 strstr() 以及 Java的 indexOf() 定义相符。
解法
Java
class Solution {
public int strStr(String haystack, String needle) {
if ("".equals(needle)) {
return 0;
}
int len1 = haystack.length();
int len2 = needle.length();
int p = 0;
int q = 0;
while (p < len1) {
if (haystack.charAt(p) == needle.charAt(q)) {
if (len2 == 1) {
return p;
}
++p;
++q;
} else {
p -= q - 1;
q = 0;
}
if (q == len2) {
return p - q;
}
}
return -1;
}
}
29. 两数相除
题目描述
给定两个整数,被除数 dividend
和除数 divisor
。将两数相除,要求不使用乘法、除法和 mod 运算符。
返回被除数 dividend
除以除数 divisor
得到的商。
整数除法的结果应当截去(truncate
)其小数部分,例如:truncate(8.345) = 8
以及 truncate(-2.7335) = -2
示例 1:
输入: dividend = 10, divisor = 3 输出: 3 解释: 10/3 = truncate(3.33333..) = truncate(3) = 3
示例 2:
输入: dividend = 7, divisor = -3 输出: -2 解释: 7/-3 = truncate(-2.33333..) = truncate(-2) = 3
提示:
- 被除数和除数均为 32 位有符号整数。
- 除数不为 0。
- 假设我们的环境只能存储 32 位有符号整数,其数值范围是 [−231, 231 − 1]。本题中,如果除法结果溢出,则返回 231 − 1。
解法
Java
class Solution {
public int divide(int dividend, int divisor) {
if(dividend == 0 || divisor == 1) {
return dividend;
}
if(divisor == 0 || (dividend == Integer.MIN_VALUE && divisor == -1)) {
return Integer.MAX_VALUE;
}
// 商的符号,true 为正,false 为负
boolean flag = true;
if((dividend < 0 && divisor > 0) || (dividend > 0 && divisor < 0)) {
flag = false;
}
long dividendLong = Math.abs((long)dividend);
long divisorLong = Math.abs((long)divisor);
int re = 0;
long factor = 0x1;
while (dividendLong >= (divisorLong << 1)) {
divisorLong <<= 1;
factor <<= 1;
}
while (factor > 0 && dividendLong > 0) {
if(dividendLong >= divisorLong) {
dividendLong -= divisorLong;
re += factor;
}
factor >>>= 1;
divisorLong >>>= 1;
}
return flag ? re : -re;
}
}
30. 串联所有单词的子串
题目描述
给定一个字符串 s 和一些长度相同的单词 words。找出 s 中恰好可以由 words 中所有单词串联形成的子串的起始位置。
注意子串要与 words 中的单词完全匹配,中间不能有其他字符,但不需要考虑 words 中单词串联的顺序。
示例 1:
输入:
s = "barfoothefoobarman",
words = ["foo","bar"]
输出:[0,9]
解释:
从索引 0 和 9 开始的子串分别是 "barfoo" 和 "foobar" 。
输出的顺序不重要, [9,0] 也是有效答案。
示例 2:
输入:
s = "wordgoodgoodgoodbestword",
words = ["word","good","best","word"]
输出:[]
解法
Java
class Solution {
public List<Integer> findSubstring(String s, String[] words) {
List<Integer> re = new ArrayList<>();
if(s == null || words == null || words.length == 0 || words[0] == null) {
return re;
}
if(s.length() == 0 || words[0].length() == 0 || s.length() < words.length * words[0].length()) {
return re;
}
// 用< 单词,出现次数 > 来存储 words 中的元素,方便查找
HashMap<String,Integer> map = new HashMap();
for (String string : words) {
map.put(string, map.getOrDefault(string,0) + 1);
}
int len = words[0].length();
int strLen = s.length();
int lastStart = len * words.length - len;
for (int i = 0; i < len; i++) {
for (int j = i; j <= strLen - len - lastStart; j += len) {
String tempStr = s.substring(j, j + len);
if(map.containsKey(tempStr)) {
HashMap<String,Integer> searched = new HashMap<>();
// 从后向前依次对比
int tempIndex = j + lastStart;
String matchedStr = s.substring(tempIndex, tempIndex + len);
while (tempIndex >= j && map.containsKey(matchedStr)) {
// 正确匹配到单词
if(searched.getOrDefault(matchedStr,0) < map.get(matchedStr)) {
searched.put(matchedStr, searched.getOrDefault(matchedStr,0) + 1);
}
else {
break;
}
tempIndex -= len;
if(tempIndex < j) {
break;
}
matchedStr = s.substring(tempIndex, tempIndex + len);
}
// 完全匹配所以单词
if(j > tempIndex) {
re.add(j);
}
// 从tempIndex 到 tempIndex + len 这个单词不能正确匹配
else {
j = tempIndex;
}
}
}
}
return re;
}
}
31. 下一个排列
题目描述
实现获取下一个排列的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。
如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。
必须原地修改,只允许使用额外常数空间。
以下是一些例子,输入位于左侧列,其相应输出位于右侧列。
1,2,3
→ 1,3,2
3,2,1
→ 1,2,3
1,1,5
→ 1,5,1
解法
Java
class Solution {
public void nextPermutation(int[] nums) {
boolean flag = false;
for (int i = nums.length - 2; i >= 0; --i) {
if (nums[i] < nums[i + 1]) {
int index = findMinIndex(nums, i, nums[i]);
swap(nums, i, index);
reverse(nums, i + 1);
flag = true;
break;
}
}
if (!flag) {
Arrays.sort(nums);
}
}
private void reverse(int[] nums, int start) {
int end = nums.length - 1;
while (start < end) {
swap(nums, start++, end--);
}
}
/**
* 找出从start开始的比val大的最小元素的下标,如果有多个,选择后者
*
* @param name
* @param start
* @param val
* @return index
*/
private int findMinIndex(int[] nums, int start, int val) {
int end = nums.length - 1;
int i = start;
for (; i < end; ++i) {
if (nums[i + 1] <= val) {
break;
}
}
return i;
}
private void swap(int[] nums, int i, int j) {
int t = nums[i];
nums[i] = nums[j];
nums[j] = t;
}
}
32. 最长有效括号
题目描述
给定一个只包含 '('
和 ')'
的字符串,找出最长的包含有效括号的子串的长度。
示例 1:
输入: "(()"
输出: 2
解释: 最长有效括号子串为 "()"
示例 2:
输入: ")()())
" 输出: 4 解释: 最长有效括号子串为"()()"
解法
Java
class Solution {
public int longestValidParentheses(String s) {
if (s == null || s.length() < 2) {
return 0;
}
char[] chars = s.toCharArray();
int n = chars.length;
int[] res = new int[n];
res[0] = 0;
res[1] = chars[1] == ')' && chars[0] == '(' ? 2 : 0;
int max = res[1];
for (int i = 2; i < n; ++i) {
if (chars[i] == ')') {
if (chars[i - 1] == '(') {
res[i] = res[i - 2] + 2;
} else {
int index = i - res[i - 1] - 1;
if (index >= 0 && chars[index] == '(') {
// ()(())
res[i] = res[i - 1] + 2 + (index - 1 >= 0 ? res[index - 1] : 0);
}
}
}
max = Math.max(max, res[i]);
}
return max;
}
}
33. 搜索旋转排序数组
题目描述
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,1,2,4,5,6,7]
可能变为 [4,5,6,7,0,1,2]
)。
搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1
。
你可以假设数组中不存在重复的元素。
你的算法时间复杂度必须是 O(log n) 级别。
示例 1:
输入: nums = [4,5,6,7,0,1,2]
, target = 0
输出: 4
示例 2:
输入: nums = [4,5,6,7,0,1,2]
, target = 3
输出: -1
解法
Java
class Solution {
public int search(int[] A, int target) {
if (A == null || A.length == 0) return -1;
int low = 0,high = A.length - 1;
while (low <= high) {
int mid = low + ((high - low) >> 1);
if (target < A[mid]) {
if (A[mid] >= A[high] && target < A[low]) low = mid + 1;
else high = mid - 1;
} else if (target > A[mid]) {
if (A[low] >= A[mid] && target > A[high]) high = mid - 1;
else low = mid + 1;
} else return mid;
}
return -1;
}
}
34. 在排序数组中查找元素的第一个和最后一个位置
题目描述
给定一个按照升序排列的整数数组 nums
,和一个目标值 target
。找出给定目标值在数组中的开始位置和结束位置。
你的算法时间复杂度必须是 O(log n) 级别。
如果数组中不存在目标值,返回 [-1, -1]
。
示例 1:
输入: nums = [5,7,7,8,8,10]
, target = 8
输出: [3,4]
示例 2:
输入: nums = [5,7,7,8,8,10]
, target = 6
输出: [-1,-1]
解法
Java
class Solution {
public int[] searchRange(int[] nums, int target) {
int start = 0, end = nums.length - 1;
while (start <= end) {
int mid = start + (end - start) / 2;
if (nums[mid] < target) start = mid + 1;
else if (nums[mid] > target) end = mid - 1;
if (nums[mid] == target) {
return new int[]{findFirst(nums, start, mid, target),findEnd(nums, mid, end, target)};
}
}
return new int[]{-1,-1};
}
private int findFirst(int[] nums, int start, int end, int target) {
while (start < end) {
int temp = start + (end - start) / 2;
if (nums[temp] < target) start = temp + 1;
else if (nums[temp] == target) end = temp;
}
return start;
}
private int findEnd(int[] nums, int start, int end, int target) {
while (start < end) {
int temp = start + (end - start + 1) / 2;
if (nums[temp] > target) end = temp - 1;
else if (nums[temp] == target) start = temp;
}
return start;
}
}
35. 搜索插入位置
题目描述
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
你可以假设数组中无重复元素。
示例 1:
输入: [1,3,5,6], 5 输出: 2
示例 2:
输入: [1,3,5,6], 2 输出: 1
示例 3:
输入: [1,3,5,6], 7 输出: 4
示例 4:
输入: [1,3,5,6], 0 输出: 0
解法
二分查找。
Java
class Solution {
public int searchInsert(int[] nums, int target) {
int l = 0, h = nums.length - 1;
while (l <= h) {
int m = l + ((h - l) >> 1);
if (nums[m] == target) return m;
if (nums[m] < target) l = m + 1;
else h = m - 1;
}
return l;
}
}
36. 有效的数独
题目描述
判断一个 9x9 的数独是否有效。只需要根据以下规则,验证已经填入的数字是否有效即可。
- 数字
1-9
在每一行只能出现一次。 - 数字
1-9
在每一列只能出现一次。 - 数字
1-9
在每一个以粗实线分隔的3x3
宫内只能出现一次。
上图是一个部分填充的有效的数独。
数独部分空格内已填入了数字,空白格用 '.'
表示。
示例 1:
输入: [ ["5","3",".",".","7",".",".",".","."], ["6",".",".","1","9","5",".",".","."], [".","9","8",".",".",".",".","6","."], ["8",".",".",".","6",".",".",".","3"], ["4",".",".","8",".","3",".",".","1"], ["7",".",".",".","2",".",".",".","6"], [".","6",".",".",".",".","2","8","."], [".",".",".","4","1","9",".",".","5"], [".",".",".",".","8",".",".","7","9"] ] 输出: true
示例 2:
输入: [ ["8","3",".",".","7",".",".",".","."], ["6",".",".","1","9","5",".",".","."], [".","9","8",".",".",".",".","6","."], ["8",".",".",".","6",".",".",".","3"], ["4",".",".","8",".","3",".",".","1"], ["7",".",".",".","2",".",".",".","6"], [".","6",".",".",".",".","2","8","."], [".",".",".","4","1","9",".",".","5"], [".",".",".",".","8",".",".","7","9"] ] 输出: false 解释: 除了第一行的第一个数字从 5 改为 8 以外,空格内其他数字均与 示例1 相同。 但由于位于左上角的 3x3 宫内有两个 8 存在, 因此这个数独是无效的。
说明:
- 一个有效的数独(部分已被填充)不一定是可解的。
- 只需要根据以上规则,验证已经填入的数字是否有效即可。
- 给定数独序列只包含数字
1-9
和字符'.'
。 - 给定数独永远是
9x9
形式的。
解法
Java
class Solution {
public boolean isValidSudoku(char[][] board) {
for(int i = 0; i < 9; i++) {
HashSet<Character> col = new HashSet<>() , row = new HashSet<>() , cube = new HashSet<>();
for(int j = 0; j < 9; j++) {
if(board[i][j] != '.' && !row.add(board[i][j])) return false;
if(board[j][i] != '.' && !col.add(board[j][i])) return false;
int colIndex = i/3*3+j/3 , rowIndex = i%3*3+j%3;
if(board[rowIndex][colIndex] != '.' && !cube.add(board[rowIndex][colIndex])) return false;
}
}
return true;
}
}
37. 解数独
题目描述
编写一个程序,通过已填充的空格来解决数独问题。
一个数独的解法需遵循如下规则:
- 数字
1-9
在每一行只能出现一次。 - 数字
1-9
在每一列只能出现一次。 - 数字
1-9
在每一个以粗实线分隔的3x3
宫内只能出现一次。
空白格用 '.'
表示。
一个数独。
答案被标成红色。
Note:
- 给定的数独序列只包含数字
1-9
和字符'.'
。 - 你可以假设给定的数独只有唯一解。
- 给定数独永远是
9x9
形式的。
解法
Java
class Solution {
private char[][] board;
private boolean[][] rows;
private boolean[][] cols;
private boolean[][] subBox;
public void solveSudoku(char[][] board) {
this.board = board;
this.rows = new boolean[9][9];
this.cols = new boolean[9][9];
this.subBox = new boolean[9][9];
int val;
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
if (board[i][j] != '.') {
val = board[i][j] - '0';
rows[i][val - 1] = true;
cols[j][val - 1] = true;
subBox[(i / 3) * 3 + j / 3][val - 1] = true;
}
}
}
fillSudoku(0, 0);
}
private boolean fillSudoku(int i, int j) {
if (i == 9) return true;
int x = i, y = j;
if (y == 8) {
x++;
y = 0;
} else y++;
if (board[i][j] == '.') {
for (int k = 1; k <= 9; k++) {
if (!rows[i][k - 1] && !cols[j][k - 1] && !subBox[(i / 3) * 3 + j / 3][k - 1]) {
rows[i][k - 1] = true;
cols[j][k - 1] = true;
subBox[(i / 3) * 3 + j / 3][k - 1] = true;
if (fillSudoku(x, y)) {
board[i][j] = (char) (k + '0');
return true;
}
rows[i][k - 1] = false;
cols[j][k - 1] = false;
subBox[(i / 3) * 3 + j / 3][k - 1] = false;
}
}
return false;
} else return fillSudoku(x, y);
}
}
38. 外观数列
题目描述
「外观数列」是一个整数序列,从数字 1 开始,序列中的每一项都是对前一项的描述。前五项如下:
1. 1 2. 11 3. 21 4. 1211 5. 111221
1
被读作 "one 1"
("一个一"
) , 即 11
。
11
被读作 "two 1s"
("两个一"
), 即 21
。
21
被读作 "one 2"
, "one 1"
("一个二"
, "一个一"
) , 即 1211
。
给定一个正整数 n(1 ≤ n ≤ 30),输出外观数列的第 n 项。
注意:整数序列中的每一项将表示为一个字符串。
示例 1:
输入: 1 输出: "1" 解释:这是一个基本样例。
示例 2:
输入: 4 输出: "1211" 解释:当 n = 3 时,序列是 "21",其中我们有 "2" 和 "1" 两组,"2" 可以读作 "12",也就是出现频次 = 1 而 值 = 2;类似 "1" 可以读作 "11"。所以答案是 "12" 和 "11" 组合在一起,也就是 "1211"。
解法
Java
class Solution {
public String countAndSay(int n) {
String one = "1";
while (n > 1) {
one = say(one);
n--;
}
return one;
}
private String say(String las) {
StringBuilder sBuilder = new StringBuilder();
int l = 1;
for (int i = 0; i < las.length(); i++) {
if (i < las.length() - 1 && las.charAt(i) == las.charAt(i + 1)) l++;
else {
sBuilder.append(l).append(las.charAt(i));
l = 1;
}
}
return sBuilder.toString();
}
}
39. 组合总和
题目描述
给定一个无重复元素的数组 candidates
和一个目标数 target
,找出 candidates
中所有可以使数字和为 target
的组合。
candidates
中的数字可以无限制重复被选取。
说明:
- 所有数字(包括
target
)都是正整数。 - 解集不能包含重复的组合。
示例 1:
输入: candidates =[2,3,6,7],
target =7
, 所求解集为: [ [7], [2,2,3] ]
示例 2:
输入: candidates = [2,3,5],
target = 8,
所求解集为:
[
[2,2,2,2],
[2,3,3],
[3,5]
]
解法
Java
class Solution {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> result = new ArrayList<>();
Arrays.sort(candidates);
combinationSum(candidates,target,candidates.length-1, new ArrayList<>(),result);
return result;
}
private void combinationSum(int[] candidates, int target,int length, List<Integer> integers,
List<List<Integer>> result) {
List<Integer> list;
for (int i = length; i >= 0; i--) {
int nc = candidates[i];
if (nc>target) continue;
list = new ArrayList<>(integers);
list.add(nc);
if (nc==target) result.add(list);
else combinationSum(candidates, target - nc, i, list,result);
}
}
}
40. 组合总和 II
题目描述
给定一个数组 candidates
和一个目标数 target
,找出 candidates
中所有可以使数字和为 target
的组合。
candidates
中的每个数字在每个组合中只能使用一次。
说明:
- 所有数字(包括目标数)都是正整数。
- 解集不能包含重复的组合。
示例 1:
输入: candidates =[10,1,2,7,6,1,5]
, target =8
, 所求解集为: [ [1, 7], [1, 2, 5], [2, 6], [1, 1, 6] ]
示例 2:
输入: candidates = [2,5,2,1,2], target = 5, 所求解集为: [ [1,2,2], [5] ]
解法
Java
class Solution {
private List<List<Integer>> result = new ArrayList<>();
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
Arrays.sort(candidates);
combinationSum(candidates,target,candidates.length-1, new ArrayList<>());
return result;
}
private void combinationSum(int[] candidates, int target,int length, List<Integer> integers) {
List<Integer> list;
for (int i = length; i >= 0; i--) {
int nc = candidates[i];
if (nc>target || i<length && nc==candidates[i+1]) continue;
list = new ArrayList<>(integers);
list.add(nc);
if (nc==target) result.add(list);
else combinationSum(candidates, target - nc, i-1, list);
}
}
}
41. 缺失的第一个正数
题目描述
给定一个未排序的整数数组,找出其中没有出现的最小的正整数。
示例 1:
输入: [1,2,0] 输出: 3
示例 2:
输入: [3,4,-1,1] 输出: 2
示例 3:
输入: [7,8,9,11,12] 输出: 1
说明:
你的算法的时间复杂度应为O(n),并且只能使用常数级别的空间。
解法
Java
public class Solution {
public int firstMissingPositive(int[] num) {
for (int i = 0; i < num.length; i++) {
if (num[i] > 0 && num[i] < num.length && num[num[i] - 1] != num[i]) {
swap(num, i, num[i] - 1);
i--;
}
}
for (int i = 0; i < num.length; i++) {
if (i + 1 != num[i]) {
return i + 1;
}
}
return num.length + 1;
}
private void swap(int[] num, int i, int j) {
int temp = num[i];
num[i] = num[j];
num[j] = temp;
}
}
42. 接雨水
题目描述
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。 感谢 Marcos 贡献此图。
示例:
输入: [0,1,0,2,1,0,1,3,2,1,2,1] 输出: 6
解法
Java
class Solution {
public int trap(int[] height) {
if (height == null || height.length == 0) return 0;
int lx = 0, rx = height.length - 1, l = height[lx], r = height[rx], re = 0;
while (lx < rx) {
if (l < r) {
lx++;
if (height[lx] < l) re += l - height[lx];
else l = height[lx];
} else {
rx--;
if (height[rx] < r) re += r - height[rx];
else r = height[rx];
}
}
return re;
}
}
43. 字符串相乘
题目描述
给定两个以字符串形式表示的非负整数 num1
和 num2
,返回 num1
和 num2
的乘积,它们的乘积也表示为字符串形式。
示例 1:
输入: num1 = "2", num2 = "3" 输出: "6"
示例 2:
输入: num1 = "123", num2 = "456" 输出: "56088"
说明:
num1
和num2
的长度小于110。num1
和num2
只包含数字0-9
。num1
和num2
均不以零开头,除非是数字 0 本身。- 不能使用任何标准库的大数类型(比如 BigInteger)或直接将输入转换为整数来处理。
解法
Java
class Solution {
public String multiply(String num1, String num2) {
char[] chars1 = num1.toCharArray(),chars2 = num2.toCharArray();
int[] result = new int[chars1.length+chars2.length];
int pow = result.length-1;
for (int i = chars1.length - 1; i >= 0; i--) {
int a = chars1[i] - '0';
int j = 0,bit = pow;
for (int i1 = chars2.length - 1; i1 >= 0; i1--) {
int b = chars2[i1] -'0';
j = a*b + j + result[bit];
result[bit--] = j%10;
j = j/10;
}
while (j!=0){
j += result[bit];
result[bit--] = j%10;
j = j/10;
}
pow--;
}
StringBuilder builder = new StringBuilder();
int i = 0;
for (; i < result.length; i++) if (result[i] != 0) break;
for (; i < result.length; i++) builder.append(result[i]);
return builder.length()==0? "0" : builder.toString();
}
}
44. 通配符匹配
题目描述
给定一个字符串 (s
) 和一个字符模式 (p
) ,实现一个支持 '?'
和 '*'
的通配符匹配。
'?' 可以匹配任何单个字符。 '*' 可以匹配任意字符串(包括空字符串)。
两个字符串完全匹配才算匹配成功。
说明:
s
可能为空,且只包含从a-z
的小写字母。p
可能为空,且只包含从a-z
的小写字母,以及字符?
和*
。
示例 1:
输入: s = "aa" p = "a" 输出: false 解释: "a" 无法匹配 "aa" 整个字符串。
示例 2:
输入: s = "aa" p = "*" 输出: true 解释: '*' 可以匹配任意字符串。
示例 3:
输入: s = "cb" p = "?a" 输出: false 解释: '?' 可以匹配 'c', 但第二个 'a' 无法匹配 'b'。
示例 4:
输入: s = "adceb" p = "*a*b" 输出: true 解释: 第一个 '*' 可以匹配空字符串, 第二个 '*' 可以匹配字符串 "dce".
示例 5:
输入: s = "acdcb" p = "a*c?b" 输入: false
解法
Java
class Solution {
public boolean isMatch(String s, String p) {
int i = 0, j = 0, is = -1, ip = -1;
char[] ss = s.toCharArray();
char[] pp = p.toCharArray();
while (i < ss.length) {
if (j < pp.length && (ss[i] == pp[j] || pp[j] == '?')) {
i++;
j++;
} else if (j < pp.length && pp[j] == '*') {
ip = j++;
is = i;
} else if (ip != -1) {
j = ip + 1;
i = ++is;
} else return false;
}
while (j < pp.length && pp[j] == '*') j++;
return j == p.length();
}
}
45. 跳跃游戏 II
题目描述
给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
你的目标是使用最少的跳跃次数到达数组的最后一个位置。
示例:
输入: [2,3,1,1,4] 输出: 2 解释: 跳到最后一个位置的最小跳跃数是2
。 从下标为 0 跳到下标为 1 的位置,跳1
步,然后跳3
步到达数组的最后一个位置。
说明:
假设你总是可以到达数组的最后一个位置。
解法
Java
class Solution {
public int jump(int[] nums) {
int cnt = 0,last = 0, next = 1;
for (;next < nums.length;cnt++){
int i = last;
last = next;
for (; i < last; ++i) if (i + nums[i] >= next) next = i + nums[i] + 1;
}
return cnt;
}
}
46. 全排列
题目描述
给定一个没有重复数字的序列,返回其所有可能的全排列。
示例:
输入: [1,2,3] 输出: [ [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1] ]
解法
Java
class Solution {
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> list = new ArrayList<>();
permute(list, nums, 0);
return list;
}
private void permute(List<List<Integer>> list, int[] nums, int start) {
int end = nums.length - 1;
if (start == end) {
list.add(Arrays.stream(nums).boxed().collect(Collectors.toList()));
return;
}
for (int i = start; i <= end; ++i) {
swap(nums, i, start);
permute(list, nums, start + 1);
swap(nums, i, start);
}
}
private static void swap(int[] nums, int i, int j) {
int t = nums[i];
nums[i] = nums[j];
nums[j] = t;
}
}
47. 全排列 II
题目描述
给定一个可包含重复数字的序列,返回所有不重复的全排列。
示例:
输入: [1,1,2] 输出: [ [1,1,2], [1,2,1], [2,1,1] ]
解法
Java
class Solution {
public List<List<Integer>> permuteUnique(int[] nums) {
List<List<Integer>> list = new ArrayList<>();
permute(list, nums, 0);
return list;
}
private void permute(List<List<Integer>> list, int[] nums, int start) {
int end = nums.length - 1;
if (start == end) {
List<Integer> tmp = new ArrayList<>();
for (int val : nums) {
tmp.add(val);
}
list.add(tmp);
}
for (int i = start; i <= end; ++i) {
if (isSwap(nums, start, i)) {
swap(nums, i, start);
permute(list, nums, start + 1);
swap(nums, i, start);
}
}
}
private boolean isSwap(int[] nums, int from, int to) {
for (int i = from; i < to; ++i) {
if (nums[i] == nums[to]) {
// 相等,不进行交换
return false;
}
}
return true;
}
private void swap(int[] nums, int i, int j) {
int t = nums[i];
nums[i] = nums[j];
nums[j] = t;
}
}
48. 旋转图像
题目描述
给定一个 n × n 的二维矩阵表示一个图像。
将图像顺时针旋转 90 度。
说明:
你必须在原地旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要使用另一个矩阵来旋转图像。
示例 1:
给定 matrix = [ [1,2,3], [4,5,6], [7,8,9] ], 原地旋转输入矩阵,使其变为: [ [7,4,1], [8,5,2], [9,6,3] ]
示例 2:
给定 matrix = [ [ 5, 1, 9,11], [ 2, 4, 8,10], [13, 3, 6, 7], [15,14,12,16] ], 原地旋转输入矩阵,使其变为: [ [15,13, 2, 5], [14, 3, 4, 1], [12, 6, 8, 9], [16, 7,10,11] ]
解法
Java
class Solution {
public void rotate(int[][] matrix) {
int s = 0, n = matrix.length;
while (s < (n >> 1)) {
int e = n - s - 1;
for (int i = s; i < e; ++i) {
int t = matrix[i][e];
matrix[i][e] = matrix[s][i];
matrix[s][i] = matrix[n - i - 1][s];
matrix[n - i - 1][s] = matrix[e][n - i - 1];
matrix[e][n - i - 1] = t;
}
++s;
}
}
}
49. 字母异位词分组
题目描述
给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。
示例:
输入: ["eat", "tea", "tan", "ate", "nat", "bat"]
,
输出:
[
["ate","eat","tea"],
["nat","tan"],
["bat"]
]
说明:
- 所有输入均为小写字母。
- 不考虑答案输出的顺序。
解法
Java
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
Map<String, List<String>> map = new HashMap<>();
for (String str : strs) {
char[] chars = str.toCharArray();
Arrays.sort(chars);
String key = new String(chars);
if (!map.containsKey(key)) map.put(key, new ArrayList<>());
map.get(key).add(str);
}
return new ArrayList<>(map.values());
}
}
50. Pow(x, n)
题目描述
实现 pow(x, n) ,即计算 x 的 n 次幂函数。
示例 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
说明:
- -100.0 < x < 100.0
- n 是 32 位有符号整数,其数值范围是 [−231, 231 − 1] 。
解法
Java
class Solution {
public double myPow(double x, int n) {
if(n < 0) return sum(1.0 / x,0 - n);
return sum(x,n);
}
public double sum(double x, int n) {
if(n == 0) return 1;
if(n == 1) return x;
if( n % 2 == 0) return sum(x * x, n / 2);
else return x * sum(x * x, n / 2);
}
}
51. N 皇后
题目描述
n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
上图为 8 皇后问题的一种解法。
给定一个整数 n,返回所有不同的 n 皇后问题的解决方案。
每一种解法包含一个明确的 n 皇后问题的棋子放置方案,该方案中 'Q'
和 '.'
分别代表了皇后和空位。
示例:
输入: 4 输出: [ [".Q..", // 解法 1 "...Q", "Q...", "..Q."], ["..Q.", // 解法 2 "Q...", "...Q", ".Q.."] ] 解释: 4 皇后问题存在两个不同的解法。
解法
Java
class Solution {
private List<List<String>> solutions;
private char[][] nQueens;
private boolean[] colUsed;
private boolean[] diagonals45Used;
private boolean[] diagonals135Used;
private int n;
public List<List<String>> solveNQueens(int n) {
solutions = new ArrayList<>();
nQueens = new char[n][n];
for (int i = 0; i < n; i++) Arrays.fill(nQueens[i], '.');
colUsed = new boolean[n];
diagonals45Used = new boolean[(2 * n) - 1];
diagonals135Used = new boolean[(2 * n) - 1];
this.n = n;
backtracking(0);
return solutions;
}
private void backtracking(int row) {
if (row == n) {
List<String> list = new ArrayList<>();
for (char[] chars : nQueens) list.add(new String(chars));
solutions.add(list);
return;
}
for (int col = 0; col < n; col++) {
int diagonals45Idx = row + col;
int diagonals135Idx = n - 1 - (row - col);
if (colUsed[col] || diagonals45Used[diagonals45Idx] || diagonals135Used[diagonals135Idx]) continue;
nQueens[row][col] = 'Q';
colUsed[col] = diagonals45Used[diagonals45Idx] = diagonals135Used[diagonals135Idx] = true;
backtracking(row + 1);
colUsed[col] = diagonals45Used[diagonals45Idx] = diagonals135Used[diagonals135Idx] = false;
nQueens[row][col] = '.';
}
}
}
52. N 皇后 II
题目描述
n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
上图为 8 皇后问题的一种解法。
给定一个整数 n,返回 n 皇后不同的解决方案的数量。
示例:
输入: 4 输出: 2 解释: 4 皇后问题存在如下两个不同的解法。 [ [".Q..", // 解法 1 "...Q", "Q...", "..Q."], ["..Q.", // 解法 2 "Q...", "...Q", ".Q.."] ]
解法
Java
class Solution {
int count = 0;
public int totalNQueens(int n) {
int[] c = new int[n];
search(0, n, c);
return count;
}
public void search(int cur, int n, int[] c) {
if (cur == n) {
count++;
return;
}
for (int i = 0; i < n; i++) {
boolean flag = true;
c[cur] = i;
for (int j = 0; j < cur; j++) {
if ((c[cur] == c[j]) || ((c[cur] - cur) == (c[j] - j)) || ((c[cur] + cur) == (c[j] + j))) {
flag = false;
break;
}
}
if (flag) search(cur + 1, n, c);
}
}
}
53. 最大子序和
题目描述
给定一个整数数组 nums
,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4], 输出: 6 解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
进阶:
如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。
解法
设 dp[i] 表示 [0..i]
中,以 nums[i]
结尾的最大子数组和,状态转移方程 dp[i] = nums[i] + max(dp[i - 1], 0)
。
由于 dp[i]
只与子问题 dp[i-1]
有关,故可以用一个变量 f 来表示。
Java
class Solution {
public int maxSubArray(int[] nums) {
int f = nums[0], res = nums[0];
for (int i = 1, n = nums.length; i < n; ++i) {
f = nums[i] + Math.max(f, 0);
res = Math.max(res, f);
}
return res;
}
}
54. 螺旋矩阵
题目描述
给定一个包含 m x n 个元素的矩阵(m 行, n 列),请按照顺时针螺旋顺序,返回矩阵中的所有元素。
示例 1:
输入: [ [ 1, 2, 3 ], [ 4, 5, 6 ], [ 7, 8, 9 ] ] 输出: [1,2,3,6,9,8,7,4,5]
示例 2:
输入: [ [1, 2, 3, 4], [5, 6, 7, 8], [9,10,11,12] ] 输出: [1,2,3,4,8,12,11,10,9,5,6,7]
提示:
- m == matrix.length
- n == matrix[i].length
- 1 <= m, n <= 10
- -100 <= matrix[i][j] <= 100
解法
从外往里一圈一圈遍历并存储矩阵元素即可。
Java
class Solution {
private List<Integer> res;
public List<Integer> spiralOrder(int[][] matrix) {
int m = matrix.length, n = matrix[0].length;
res = new ArrayList<>();
int i1 = 0, i2 = m - 1;
int j1 = 0, j2 = n - 1;
while (i1 <= i2 && j1 <= j2) {
add(matrix, i1++, j1++, i2--, j2--);
}
return res;
}
private void add(int[][] matrix, int i1, int j1, int i2, int j2) {
if (i1 == i2) {
for (int j = j1; j <= j2; ++j) {
res.add(matrix[i1][j]);
}
return;
}
if (j1 == j2) {
for (int i = i1; i <= i2; ++i) {
res.add(matrix[i][j1]);
}
return;
}
for (int j = j1; j < j2; ++j) {
res.add(matrix[i1][j]);
}
for (int i = i1; i < i2; ++i) {
res.add(matrix[i][j2]);
}
for (int j = j2; j > j1; --j) {
res.add(matrix[i2][j]);
}
for (int i = i2; i > i1; --i) {
res.add(matrix[i][j1]);
}
}
}
55. 跳跃游戏
题目描述
给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个位置。
示例 1:
输入: [2,3,1,1,4] 输出: true 解释: 我们可以先跳 1 步,从位置 0 到达 位置 1, 然后再从位置 1 跳 3 步到达最后一个位置。
示例 2:
输入: [3,2,1,0,4] 输出: false 解释: 无论怎样,你总会到达索引为 3 的位置。但该位置的最大跳跃长度是 0 , 所以你永远不可能到达最后一个位置。
解法
Java
class Solution {
public boolean canJump(int[] nums) {
int count = 0;
for (int i = nums.length - 2; i >= 0; i --) {
count = nums[i] > count ? 0 : count + 1;
}
return count == 0;
}
}
56. 合并区间
题目描述
给出一个区间的集合,请合并所有重叠的区间。
示例 1:
输入: [[1,3],[2,6],[8,10],[15,18]] 输出: [[1,6],[8,10],[15,18]] 解释: 区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
示例 2:
输入: [[1,4],[4,5]] 输出: [[1,5]] 解释: 区间 [1,4] 和 [4,5] 可被视为重叠区间。
解法
Java
/**
* Definition for an interval.
* public class Interval {
* int start;
* int end;
* Interval() { start = 0; end = 0; }
* Interval(int s, int e) { start = s; end = e; }
* }
*/
class Solution {
public List<Interval> merge(List<Interval> intervals) {
int n=intervals.size();
int[] starts=new int[n],ends=new int[n];
for(int i=0;i<n;i++){
starts[i]=intervals.get(i).start;
ends[i]=intervals.get(i).end;
}
Arrays.sort(starts);
Arrays.sort(ends);
List<Interval> res= new ArrayList<>();
for(int i=0,j=0;i<n;i++){
if((i == (n - 1)) || (starts[i + 1] > ends[i])){
res.add(new Interval(starts[j],ends[i]));
j=i+1;
}
}
return res;
}
}
57. 插入区间
题目描述
给出一个无重叠的 ,按照区间起始端点排序的区间列表。
在列表中插入一个新的区间,你需要确保列表中的区间仍然有序且不重叠(如果有必要的话,可以合并区间)。
示例 1:
输入: intervals = [[1,3],[6,9]], newInterval = [2,5] 输出: [[1,5],[6,9]]
示例 2:
输入: intervals =[[1,2],[3,5],[6,7],[8,10],[12,16]]
, newInterval =[4,8]
输出: [[1,2],[3,10],[12,16]] 解释: 这是因为新的区间[4,8]
与[3,5],[6,7],[8,10]
重叠。
解法
Java
/**
* Definition for an interval.
* public class Interval {
* int start;
* int end;
* Interval() { start = 0; end = 0; }
* Interval(int s, int e) { start = s; end = e; }
* }
*/
class Solution {
public List<Interval> insert(List<Interval> intervals, Interval newInterval) {
List<Interval> list = new LinkedList<>();
int i = 0;
while ((i < intervals.size()) && (intervals.get(i).end < newInterval.start)) list.add(intervals.get(i++));
while ((i < intervals.size()) && (intervals.get(i).start <= newInterval.end)) {
newInterval = new Interval(
Math.min(intervals.get(i).start, newInterval.start),
Math.max(intervals.get(i).end, newInterval.end)
);
i++;
}
list.add(newInterval);
while (i < intervals.size()) list.add(intervals.get(i++));
return list;
}
}
58. 最后一个单词的长度
题目描述
给定一个仅包含大小写字母和空格 ' '
的字符串 s
,返回其最后一个单词的长度。如果字符串从左向右滚动显示,那么最后一个单词就是最后出现的单词。
如果不存在最后一个单词,请返回 0 。
说明:一个单词是指仅由字母组成、不包含任何空格字符的 最大子字符串。
示例:
输入: "Hello World" 输出: 5
解法
Java
class Solution {
public int lengthOfLastWord(String s) {
int n = s.length();
int lastWordLength = 0;
boolean meetWord = false;
for (int i = n - 1; i >= 0; --i) {
char ch = s.charAt(i);
if (ch >= 'A' && ch <= 'z') {
meetWord = true;
++lastWordLength;
} else if (meetWord) {
break;
}
}
return lastWordLength;
}
}
59. 螺旋矩阵 II
题目描述
给定一个正整数 n,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的正方形矩阵。
示例:
输入: 3 输出: [ [ 1, 2, 3 ], [ 8, 9, 4 ], [ 7, 6, 5 ] ]
解法
Java
class Solution {
public int[][] generateMatrix(int n) {
int[][] res = new int[n][n];
int num = 1;
int m1 = 0, m2 = n - 1;
while (m1 < m2) {
for (int j = m1; j < m2; ++j) {
res[m1][j] = num++;
}
for (int i = m1; i < m2; ++i) {
res[i][m2] = num++;
}
for (int j = m2; j > m1; --j) {
res[m2][j] = num++;
}
for (int i = m2; i > m1; --i) {
res[i][m1] = num++;
}
++m1;
--m2;
}
if (m1 == m2) {
res[m1][m1] = num;
}
return res;
}
}
60. 第 k 个排列
题目描述
给出集合 [1,2,3,…,n]
,其所有元素共有 n! 种排列。
按大小顺序列出所有排列情况,并一一标记,当 n = 3 时, 所有排列如下:
"123"
"132"
"213"
"231"
"312"
"321"
给定 n 和 k,返回第 k 个排列。
说明:
- 给定 n 的范围是 [1, 9]。
- 给定 k 的范围是[1, n!]。
示例 1:
输入: n = 3, k = 3 输出: "213"
示例 2:
输入: n = 4, k = 9 输出: "2314"
解法
Java
class Solution {
public String getPermutation(int n, int k) {
StringBuilder s = new StringBuilder();
int[] fact = new int[n];
fact[0] = 1;
for (int i = 1; i < n; i++)
fact[i] = fact[i - 1] * i;
List<Integer> list = new ArrayList<>();
for (int i = 1; i <= n; i++)
list.add(i);
k--;
for (int i = n; i >= 1; i--) {
int j=k/fact[i-1];
k=k%fact[i-1];
s.append(list.get(j));
list.remove(j);
}
return s.toString();
}
}
61. 旋转链表
题目描述
给定一个链表,旋转链表,将链表每个节点向右移动 k 个位置,其中 k 是非负数。
示例 1:
输入: 1->2->3->4->5->NULL, k = 2 输出: 4->5->1->2->3->NULL 解释: 向右旋转 1 步: 5->1->2->3->4->NULL 向右旋转 2 步: 4->5->1->2->3->NULL
示例 2:
输入: 0->1->2->NULL, k = 4 输出:2->0->1->NULL
解释: 向右旋转 1 步: 2->0->1->NULL 向右旋转 2 步: 1->2->0->NULL 向右旋转 3 步:0->1->2->NULL
向右旋转 4 步:2->0->1->NULL
解法
将链表右半部分的 k 的节点拼接到 head 即可。
注:k 对链表长度 n 取余,即 k %= n
。
Java
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode rotateRight(ListNode head, int k) {
if (head == null || head.next == null) {
return head;
}
int n = 0;
ListNode cur = head;
while (cur != null) {
++n;
cur = cur.next;
}
k %= n;
if (k == 0) {
return head;
}
ListNode p = head, q = head;
for (int i = 0; i < k; ++i) {
q = q.next;
}
while (q.next != null) {
p = p.next;
q = q.next;
}
ListNode start = p.next;
p.next = null;
q.next = head;
return start;
}
}
62. 不同路径
题目描述
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。
问总共有多少条不同的路径?
例如,上图是一个7 x 3 的网格。有多少可能的路径?
示例 1:
输入: m = 3, n = 2 输出: 3 解释: 从左上角开始,总共有 3 条路径可以到达右下角。 1. 向右 -> 向右 -> 向下 2. 向右 -> 向下 -> 向右 3. 向下 -> 向右 -> 向右
示例 2:
输入: m = 7, n = 3 输出: 28
提示:
1 <= m, n <= 100
- 题目数据保证答案小于等于
2 * 10 ^ 9
解法
Java
class Solution {
public int uniquePaths(int m, int n) {
int[][] res = new int[n][m];
for (int i = 0; i < m; ++i) {
res[0][i] = 1;
}
for (int i = 1; i < n; ++i) {
res[i][0] = 1;
}
for (int i = 1; i < n; ++i) {
for (int j = 1; j < m; ++j) {
res[i][j] = res[i - 1][j] + res[i][j - 1];
}
}
return res[n - 1][m - 1];
}
}
63. 不同路径 II
题目描述
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。
现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?
网格中的障碍物和空位置分别用 1
和 0
来表示。
说明:m 和 n 的值均不超过 100。
示例 1:
输入:
[
[0,0,0],
[0,1,0],
[0,0,0]
]
输出: 2
解释:
3x3 网格的正中间有一个障碍物。
从左上角到右下角一共有 2
条不同的路径:
1. 向右 -> 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右 -> 向右
解法
64. 最小路径和
题目描述
给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。
示例:
输入: [ [1,3,1], [1,5,1], [4,2,1] ] 输出: 7 解释: 因为路径 1→3→1→1→1 的总和最小。
解法
Java
class Solution {
public int minPathSum(int[][] grid) {
for (int i = 1; i < grid.length; i++) grid[i][0] += grid[i - 1][0];
for (int j = 1; j < grid[0].length; j++) grid[0][j] += grid[0][j - 1];
for (int i = 1; i < grid.length; i++)
for (int j = 1; j < grid[0].length; j++) grid[i][j] += Math.min(grid[i - 1][j], grid[i][j - 1]);
return grid[grid.length-1][grid[0].length-1];
}
}
65. 有效数字
题目描述
验证给定的字符串是否可以解释为十进制数字。
例如:
"0"
=> true
" 0.1 "
=> true
"abc"
=> false
"1 a"
=> false
"2e10"
=> true
" -90e3 "
=> true
" 1e"
=> false
"e3"
=> false
" 6e-1"
=> true
" 99e2.5 "
=> false
"53.5e93"
=> true
" --6 "
=> false
"-+3"
=> false
"95a54e53"
=> false
说明: 我们有意将问题陈述地比较模糊。在实现代码之前,你应当事先思考所有可能的情况。这里给出一份可能存在于有效十进制数字中的字符列表:
- 数字 0-9
- 指数 - "e"
- 正/负号 - "+"/"-"
- 小数点 - "."
当然,在输入中,这些字符的上下文也很重要。
更新于 2015-02-10:
C++
函数的形式已经更新了。如果你仍然看见你的函数接收 const char *
类型的参数,请点击重载按钮重置你的代码。
解法
Java
class Solution {
public boolean isNumber(String s) {
if(null==s || 0==s.length()) return false;
int start=0,end=s.length()-1;
char[] c=s.toCharArray();
while(start<=end && ' '==c[start]) start++;
while(end>=start && ' '==c[end] ) end--;
if(start>end) return false;
if('+'==c[start] || '-'==c[start]) start++;
boolean hasNum=false;
boolean hasDot=false;
boolean hasE=false;
while(start<=end){
if(c[start]>='0' && c[start]<='9') hasNum = true;
else if(c[start]=='e'){
if(hasE || !hasNum) return false;
hasE=true;
hasNum=false;
}
else if(c[start]=='.'){
if(hasDot || hasE) return false;
hasDot=true;
}
else if('+'==c[start] || '-'==c[start]){
if(c[start-1]!='e') return false;
}else return false;
start++;
}
return hasNum;
}
}
66. 加一
题目描述
给定一个由整数组成的非空数组所表示的非负整数,在该数的基础上加一。
最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。
你可以假设除了整数 0 之外,这个整数不会以零开头。
示例 1:
输入: [1,2,3] 输出: [1,2,4] 解释: 输入数组表示数字 123。
示例 2:
输入: [4,3,2,1] 输出: [4,3,2,2] 解释: 输入数组表示数字 4321。
解法
Java
class Solution {
public int[] plusOne(int[] digits) {
for (int i = digits.length - 1; i >= 0; i --) {
digits[i] ++;
digits[i] = digits[i] % 10;
if (digits[i] != 0) return digits;
}
digits = new int[digits.length + 1];
digits[0] = 1;
return digits;
}
}
67. 二进制求和
题目描述
给定两个二进制字符串,返回他们的和(用二进制表示)。
输入为非空字符串且只包含数字 1
和 0
。
示例 1:
输入: a = "11", b = "1" 输出: "100"
示例 2:
输入: a = "1010", b = "1011" 输出: "10101"
解法
Java
class Solution {
public String addBinary(String a, String b) {
StringBuilder reverseAnswer = new StringBuilder();
int maxLength = Math.max(a.length(), b.length());
int carry = 0;
for (int i = 0;i < maxLength;i++) {
carry += i < a.length() ? a.charAt(a.length() - 1 - i) - 48 : 0;
carry += i < b.length() ? b.charAt(b.length() - 1 - i) - 48 : 0;
reverseAnswer.append(carry % 2);
carry /= 2;
}
if (carry == 1) {
reverseAnswer.append(1);
}
return reverseAnswer.reverse().toString();
}
}
68. 文本左右对齐
题目描述
给定一个单词数组和一个长度 maxWidth,重新排版单词,使其成为每行恰好有 maxWidth 个字符,且左右两端对齐的文本。
你应该使用“贪心算法”来放置给定的单词;也就是说,尽可能多地往每行中放置单词。必要时可用空格 ' '
填充,使得每行恰好有 maxWidth 个字符。
要求尽可能均匀分配单词间的空格数量。如果某一行单词间的空格不能均匀分配,则左侧放置的空格数要多于右侧的空格数。
文本的最后一行应为左对齐,且单词之间不插入额外的空格。
说明:
- 单词是指由非空格字符组成的字符序列。
- 每个单词的长度大于 0,小于等于 maxWidth。
- 输入单词数组
words
至少包含一个单词。
示例:
输入: words = ["This", "is", "an", "example", "of", "text", "justification."] maxWidth = 16 输出: [ "This is an", "example of text", "justification. " ]
示例 2:
输入: words = ["What","must","be","acknowledgment","shall","be"] maxWidth = 16 输出: [ "What must be", "acknowledgment ", "shall be " ] 解释: 注意最后一行的格式应为 "shall be " 而不是 "shall be", 因为最后一行应为左对齐,而不是左右两端对齐。 第二行同样为左对齐,这是因为这行只包含一个单词。
示例 3:
输入: words = ["Science","is","what","we","understand","well","enough","to","explain", "to","a","computer.","Art","is","everything","else","we","do"] maxWidth = 20 输出: [ "Science is what we", "understand well", "enough to explain to", "a computer. Art is", "everything else we", "do " ]
解法
Java
class Solution {
public List<String> fullJustify(String[] words, int maxWidth) {
ArrayList<String> res = new ArrayList<>();
if (words == null || words.length == 0) return res;
int count = 0, last = 0;
for (int i = 0; i < words.length; i++) {
if (count + words[i].length() + (i - last) > maxWidth) {
int spaceNum = 0, extraNum = 0;
if (i - last - 1 > 0) {
spaceNum = (maxWidth - count) / (i - last - 1);
extraNum = (maxWidth - count) % (i - last - 1);
}
StringBuilder str = new StringBuilder();
for (int j = last; j < i; j++) {
str.append(words[j]);
if (j < i - 1) {
for (int k = 0; k < spaceNum; k++) str.append(" ");
if (extraNum > 0) str.append(" ");
extraNum--;
}
}
for (int j = str.length(); j < maxWidth; j++) str.append(" ");
res.add(str.toString());
count = 0;
last = i;
}
count += words[i].length();
}
StringBuilder str = new StringBuilder();
for (int i = last; i < words.length; i++) {
str.append(words[i]);
if (str.length() < maxWidth) str.append(" ");
}
for (int i = str.length(); i < maxWidth; i++) str.append(" ");
res.add(str.toString());
return res;
}
}
69. x 的平方根
题目描述
实现 int sqrt(int x)
函数。
计算并返回 x 的平方根,其中 x 是非负整数。
由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。
示例 1:
输入: 4 输出: 2
示例 2:
输入: 8 输出: 2 说明: 8 的平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去。
解法
Java
class Solution {
public int mySqrt(int x) {
if(x==0)return 0;
long i=x;
while(i>x/i) i = (i + x / i) / 2;
return (int)i;
}
}
70. 爬楼梯
题目描述
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
示例 1:
输入: 2 输出: 2 解释: 有两种方法可以爬到楼顶。 1. 1 阶 + 1 阶 2. 2 阶
示例 2:
输入: 3 输出: 3 解释: 有三种方法可以爬到楼顶。 1. 1 阶 + 1 阶 + 1 阶 2. 1 阶 + 2 阶 3. 2 阶 + 1 阶
解法
Java
class Solution {
public int climbStairs(int n) {
if (n < 3) {
return n;
}
int[] res = new int[n + 1];
res[1] = 1;
res[2] = 2;
for (int i = 3; i < n + 1; ++i) {
res[i] = res[i - 1] + res[i - 2];
}
return res[n];
}
}
71. 简化路径
题目描述
以 Unix 风格给出一个文件的绝对路径,你需要简化它。或者换句话说,将其转换为规范路径。
在 Unix 风格的文件系统中,一个点(.
)表示当前目录本身;此外,两个点 (..
) 表示将目录切换到上一级(指向父目录);两者都可以是复杂相对路径的组成部分。更多信息请参阅:Linux / Unix中的绝对路径 vs 相对路径
请注意,返回的规范路径必须始终以斜杠 /
开头,并且两个目录名之间必须只有一个斜杠 /
。最后一个目录名(如果存在)不能以 /
结尾。此外,规范路径必须是表示绝对路径的最短字符串。
示例 1:
输入:"/home/" 输出:"/home" 解释:注意,最后一个目录名后面没有斜杠。
示例 2:
输入:"/../" 输出:"/" 解释:从根目录向上一级是不可行的,因为根是你可以到达的最高级。
示例 3:
输入:"/home//foo/" 输出:"/home/foo" 解释:在规范路径中,多个连续斜杠需要用一个斜杠替换。
示例 4:
输入:"/a/./b/../../c/" 输出:"/c"
示例 5:
输入:"/a/../../b/../c//.//" 输出:"/c"
示例 6:
输入:"/a//bc/d//././/.." 输出:"/a/b/c"
解法
Java
class Solution {
public String simplifyPath(String path) {
List<String> dirs = new ArrayList<>();
int dirStart = 0, len = path.length();
while (dirStart < len) {
while (dirStart < len && path.charAt(dirStart) == '/') dirStart++;
int dirEnd = dirStart;
while (dirEnd < len && path.charAt(dirEnd) != '/') dirEnd++;
String dir = path.substring(dirStart, dirEnd);
if (!".".equals(dir)) {
if ("..".equals(dir)) {
if (! dirs.isEmpty()) dirs.remove(dirs.size() - 1);
} else if (dir.length() > 0) {
dirs.add(dir);
}
}
dirStart = ++dirEnd;
}
StringBuilder sb = new StringBuilder("/");
for (int i = 0; i < dirs.size(); i++) {
if (i == dirs.size() - 1) sb.append(dirs.get(i));
else sb.append(dirs.get(i)).append("/");
}
return sb.toString();
}
}
72. 编辑距离
题目描述
给定两个单词 word1 和 word2,计算出将 word1 转换成 word2 所使用的最少操作数 。
你可以对一个单词进行如下三种操作:
- 插入一个字符
- 删除一个字符
- 替换一个字符
示例 1:
输入: word1 = "horse", word2 = "ros" 输出: 3 解释: horse -> rorse (将 'h' 替换为 'r') rorse -> rose (删除 'r') rose -> ros (删除 'e')
示例 2:
输入: word1 = "intention", word2 = "execution" 输出: 5 解释: intention -> inention (删除 't') inention -> enention (将 'i' 替换为 'e') enention -> exention (将 'n' 替换为 'x') exention -> exection (将 'n' 替换为 'c') exection -> execution (插入 'u')
解法
Java
class Solution {
public int minDistance(String word1, String word2) {
return edit(word1.length(),word2.length(),word1,word2,new int[word1.length()+1][word2.length()+1]);
}
private int edit(int l, int r, String w1, String w2, int[][] dp){
if(l==0) return r;
if(r==0) return l;
if(dp[l][r]!=0) return dp[l][r];
int min = (w1.charAt(l-1)!=w2.charAt(r-1)) ?
Math.min(edit(l-1,r-1,w1,w2,dp)+1,Math.min(edit(l-1,r,w1,w2,dp)+1,edit(l,r-1,w1,w2,dp)+1))
: edit(l - 1, r - 1, w1, w2, dp);
dp[l][r] = min;
return min;
}
}
73. 矩阵置零
题目描述
给定一个 m x n 的矩阵,如果一个元素为 0,则将其所在行和列的所有元素都设为 0。请使用原地算法。
示例 1:
输入: [ [1,1,1], [1,0,1], [1,1,1] ] 输出: [ [1,0,1], [0,0,0], [1,0,1] ]
示例 2:
输入: [ [0,1,2,0], [3,4,5,2], [1,3,1,5] ] 输出: [ [0,0,0,0], [0,4,5,0], [0,3,1,0] ]
进阶:
- 一个直接的解决方案是使用 O(mn) 的额外空间,但这并不是一个好的解决方案。
- 一个简单的改进方案是使用 O(m + n) 的额外空间,但这仍然不是最好的解决方案。
- 你能想出一个常数空间的解决方案吗?
解法
Java
class Solution {
public void setZeroes(int[][] matrix) {
int matrixRow = matrix.length, matrixCol = matrix[0].length;
boolean[] row = new boolean[matrixRow], col = new boolean[matrixCol];
for (int i = 0; i < matrixRow; i++) {
for (int j = 0; j < matrixCol; j++) {
if (matrix[i][j] == 0) {
row[i] = true;
col[j] = true;
}
}
}
for (int i = 0; i < matrixRow; i++) {
if (row[i]) for (int k = 0; k < matrixCol; k++) matrix[i][k] = 0;
}
for (int j = 0; j < matrixCol; j++) {
if (col[j]) for (int k = 0; k < matrixRow; k++) matrix[k][j] = 0;
}
}
}
74. 搜索二维矩阵
题目描述
编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值。该矩阵具有如下特性:
- 每行中的整数从左到右按升序排列。
- 每行的第一个整数大于前一行的最后一个整数。
示例 1:
输入: matrix = [ [1, 3, 5, 7], [10, 11, 16, 20], [23, 30, 34, 50] ] target = 3 输出: true
示例 2:
输入: matrix = [ [1, 3, 5, 7], [10, 11, 16, 20], [23, 30, 34, 50] ] target = 13 输出: false
解法
Java
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
int x = 0,y = matrix.length-1;
if(y<0) return false;
int maxX = matrix[0].length-1;
while ((x <= maxX) && (y >= 0)) {
int cur = matrix[y][x];
if (cur == target) return false;
if (cur < target) x++;
else y--;
}
return false;
}
}
75. 颜色分类
题目描述
给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
注意:
不能使用代码库中的排序函数来解决这道题。
示例:
输入: [2,0,2,1,1,0] 输出: [0,0,1,1,2,2]
进阶:
- 一个直观的解决方案是使用计数排序的两趟扫描算法。
首先,迭代计算出0、1 和 2 元素的个数,然后按照0、1、2的排序,重写当前数组。 - 你能想出一个仅使用常数空间的一趟扫描算法吗?
解法
Java
class Solution {
public void sortColors(int[] nums) {
int p = -1;
int q = nums.length;
int cur = 0;
while (cur < q) {
if (nums[cur] == 0) {
swap(nums, cur++, ++p);
} else if (nums[cur] == 1) {
++cur;
} else {
swap(nums, --q, cur);
}
}
}
private void swap(int[] nums, int i, int j) {
int t = nums[i];
nums[i] = nums[j];
nums[j] = t;
}
}
76. 最小覆盖子串
题目描述
给你一个字符串 S、一个字符串 T,请在字符串 S 里面找出:包含 T 所有字母的最小子串。
示例:
输入: S = "ADOBECODEBANC", T = "ABC" 输出: "BANC"
说明:
- 如果 S 中不存这样的子串,则返回空字符串
""
。 - 如果 S 中存在这样的子串,我们保证它是唯一的答案。
解法
Java
class Solution {
public String minWindow(String s, String t) {
int[] count = new int['z' - 'A' + 1];
int uniq = 0;
for (int i = 0; i < t.length(); ++i) {
if (++count[t.charAt(i) - 'A'] == 1) uniq++;
}
int found = 0,i = 0,j = 0;
int minLen = Integer.MAX_VALUE;
int minJ = Integer.MAX_VALUE;
while (found < uniq) {
while (i < s.length()) {
if (found >= uniq) break;
if (--count[s.charAt(i) - 'A'] == 0) found++;
i++;
}
if (found < uniq) break;
while (j < i && count[s.charAt(j) - 'A'] < 0) count[s.charAt(j++) - 'A']++;
if (i - j < minLen) {
minLen = i - j;
minJ = j;
}
count[s.charAt(j++) - 'A']++;
found--;
}
return minLen < Integer.MAX_VALUE ? s.substring(minJ, minJ + minLen) : "";
}
}
77. 组合
题目描述
给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合。
示例:
输入: n = 4, k = 2 输出: [ [2,4], [3,4], [2,3], [1,2], [1,3], [1,4], ]
解法
Java
class Solution {
private List<List<Integer>> result;
public List<List<Integer>> combine(int n, int k) {
result = new ArrayList<>();
combine(n-k+1,k,0,1,new Integer[k]);
return result;
}
private void combine(int n, int k, int i, int start, Integer[] list) {
if (i==k) {
result.add(new ArrayList<>(Arrays.asList(list)));
return;
}
for (int j = start; j <= n+i; j++) {
list[i] = j;
combine(n,k,i+1,j+1,list);
}
}
}
78. 子集
题目描述
给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
示例:
输入: nums = [1,2,3] 输出: [ [3], [1], [2], [1,2,3], [1,3], [2,3], [1,2], [] ]
解法
Java
class Solution {
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> list = new ArrayList<>();
for (int num : nums) {
int size = list.size();
for (int j = 0; j < size; j++) {
List<Integer> temp = new ArrayList<>(list.get(j));
temp.add(num);
list.add(temp);
}
List<Integer> one = new ArrayList<>();
one.add(num);
list.add(one);
}
list.add(new ArrayList<>());
return list;
}
}
80. 删除排序数组中的重复项 II
题目描述
给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素最多出现两次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。
示例 1:
给定 nums = [1,1,1,2,2,3], 函数应返回新长度 length =5
, 并且原数组的前五个元素被修改为1, 1, 2, 2,
3 。 你不需要考虑数组中超出新长度后面的元素。
示例 2:
给定 nums = [0,0,1,1,1,1,2,3,3], 函数应返回新长度 length =7
, 并且原数组的前五个元素被修改为0
, 0, 1, 1, 2, 3, 3 。 你不需要考虑数组中超出新长度后面的元素。
说明:
为什么返回数值是整数,但输出的答案是数组呢?
请注意,输入数组是以“引用”方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。
你可以想象内部操作如下:
// nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝 int len = removeDuplicates(nums); // 在函数里修改输入数组对于调用者是可见的。 // 根据你的函数返回的长度, 它会打印出数组中该长度范围内的所有元素。 for (int i = 0; i < len; i++) { print(nums[i]); }
解法
Java
class Solution {
public int removeDuplicates(int[] nums) {
if (nums.length<3) return nums.length;
int pos = 1,flag = 1,last = nums[0];
for(int i = 1;i<nums.length;i++){
if (nums[i] == last) flag++;
else {
flag = 1;
last = nums[i];
}
if (flag <= 2) nums[pos++] = last;
}
return pos;
}
}
81. 搜索旋转排序数组 II
题目描述
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,0,1,2,2,5,6]
可能变为 [2,5,6,0,0,1,2]
)。
编写一个函数来判断给定的目标值是否存在于数组中。若存在返回 true
,否则返回 false
。
示例 1:
输入: nums = [2,5,6,0,0,1,2]
, target = 0
输出: true
示例 2:
输入: nums = [2,5,6,0,0,1,2]
, target = 3
输出: false
进阶:
- 这是 搜索旋转排序数组 的延伸题目,本题中的
nums
可能包含重复元素。 - 这会影响到程序的时间复杂度吗?会有怎样的影响,为什么?
解法
Java
class Solution {
public boolean search(int[] nums, int target) {
int start = 0, end = nums.length - 1, mid;
while(start <= end) {
mid = (start + end) / 2;
if (nums[mid] == target) return true;
if (nums[mid] < nums[end] || nums[mid] < nums[start]) {
if (target > nums[mid] && target <= nums[end]) start = mid + 1;
else end = mid - 1;
} else if (nums[mid] > nums[start] || nums[mid] > nums[end]) {
if (target < nums[mid] && target >= nums[start]) end = mid - 1;
else start = mid + 1;
} else end--;
}
return false;
}
}
82. 删除排序链表中的重复元素 II
题目描述
给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中 没有重复出现 的数字。
示例 1:
输入: 1->2->3->3->4->4->5 输出: 1->2->5
示例 2:
输入: 1->1->1->2->3 输出: 2->3
解法
Java
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if (head == null || head.next == null) {
return head;
}
if (head.val == head.next.val) {
if (head.next.next == null) {
return null;
}
if (head.val == head.next.next.val) {
return deleteDuplicates(head.next);
}
return deleteDuplicates(head.next.next);
}
head.next = deleteDuplicates(head.next);
return head;
}
}
83. 删除排序链表中的重复元素
题目描述
给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。
示例 1:
输入: 1->1->2 输出: 1->2
示例 2:
输入: 1->1->2->3->3 输出: 1->2->3
解法
Java
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if (head == null || head.next == null) {
return head;
}
head.next = deleteDuplicates(head.next);
return head.val == head.next.val ? head.next : head;
}
}
84. 柱状图中最大的矩形
题目描述
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
以上是柱状图的示例,其中每个柱子的宽度为 1,给定的高度为 [2,1,5,6,2,3]
。
图中阴影部分为所能勾勒出的最大矩形面积,其面积为 10
个单位。
示例:
输入: [2,1,5,6,2,3] 输出: 10
解法
Java
class Solution {
public int largestRectangleArea(int[] heights) {
if (heights == null || heights.length == 0) {
return 0;
}
int n = heights.length;
if (n == 1) {
return heights[0];
}
// 创建一个新的数组,数组长度为 n + 1,最后一个元素值赋为 0
// 确保在后面的遍历中,原数组最后一个元素值能得到计算
int[] heightss = new int[n + 1];
heightss[n] = 0;
for (int i = 0; i < n; ++i) {
heightss[i] = heights[i];
}
Stack<Integer> stack = new Stack<>();
int max = 0;
for (int i = 0; i <= n;) {
if (stack.isEmpty() || heightss[i] > heightss[stack.peek()]) {
stack.push(i++);
} else {
int index = stack.pop();
max = Math.max(max, heightss[index] * (stack.isEmpty() ? i : i - stack.peek() - 1));
}
}
return max;
}
}
85. 最大矩形
题目描述
给定一个仅包含 0 和 1 的二维二进制矩阵,找出只包含 1 的最大矩形,并返回其面积。
示例:
输入: [ ["1","0","1","0","0"], ["1","0","1","1","1"], ["1","1","1","1","1"], ["1","0","0","1","0"] ] 输出: 6
解法
Java
class Solution {
public int maximalRectangle(char[][] matrix) {
if(matrix==null || matrix.length==0) return 0;
int result = 0;
int[] row = new int[matrix[0].length];
for(char[] line : matrix){
update(line,row);
result = Math.max(result, largestRectangleArea(row));
}
return result;
}
private int largestRectangleArea(int[] heights) {
int[] stack = new int[1 << 10];
int length = heights.length;
int j, stackSize= 0, ma = 0, a;
for (int i = 0; i <= length; i++) {
while (stackSize > 0 &&( i==length || heights[i] < heights[stack[stackSize - 1]])) {
j = stack[--stackSize];
a = (i - (stackSize == 0 ? 0 : stack[stackSize - 1] + 1)) * (heights[j]);
if (a > ma) ma = a;
}
stack[stackSize++] = i;
}
return ma;
}
private void update(char[] line, int[] row){
for (int i = 0; i < row.length; i++) {
if (line[i] == '0') row[i] = 0;
else row[i]++;
}
}
}
86. 分隔链表
题目描述
给定一个链表和一个特定值 x,对链表进行分隔,使得所有小于 x 的节点都在大于或等于 x 的节点之前。
你应当保留两个分区中每个节点的初始相对位置。
示例:
输入: head = 1->4->3->2->5->2, x = 3 输出: 1->2->2->4->3->5
解法
Java
class Solution {
public ListNode partition(ListNode head, int x) {
ListNode leftDummy = new ListNode(-1);
ListNode rightDummy = new ListNode(-1);
ListNode leftCur = leftDummy;
ListNode rightCur = rightDummy;
while (head != null) {
if (head.val < x) {
leftCur.next = head;
leftCur = leftCur.next;
} else {
rightCur.next = head;
rightCur = rightCur.next;
}
head = head.next;
}
leftCur.next = rightDummy.next;
rightCur.next = null;
return leftDummy.next;
}
}
87. 扰乱字符串
题目描述
给定一个字符串 s1,我们可以把它递归地分割成两个非空子字符串,从而将其表示为二叉树。
下图是字符串 s1 = "great"
的一种可能的表示形式。
great / \ gr eat / \ / \ g r e at / \ a t
在扰乱这个字符串的过程中,我们可以挑选任何一个非叶节点,然后交换它的两个子节点。
例如,如果我们挑选非叶节点 "gr"
,交换它的两个子节点,将会产生扰乱字符串 "rgeat"
。
rgeat / \ rg eat / \ / \ r g e at / \ a t
我们将 "rgeat”
称作 "great"
的一个扰乱字符串。
同样地,如果我们继续交换节点 "eat"
和 "at"
的子节点,将会产生另一个新的扰乱字符串 "rgtae"
。
rgtae / \ rg tae / \ / \ r g ta e / \ t a
我们将 "rgtae”
称作 "great"
的一个扰乱字符串。
给出两个长度相等的字符串 s1 和 s2,判断 s2 是否是 s1 的扰乱字符串。
示例 1:
输入: s1 = "great", s2 = "rgeat" 输出: true
示例 2:
输入: s1 = "abcde", s2 = "caebd" 输出: false
解法
Java
class Solution {
public boolean isScramble(String s1, String s2) {
if(s1.equals(s2)) return true;
if(s1.length()!=s2.length()) return false;
int len = s1.length();
int[] count = new int[26];
for(int i = 0; i < len; i++){
count[s1.charAt(i) - 'a']++;
count[s2.charAt(i) - 'a']--;
}
for(int item : count) if (item != 0) return false;
for(int i = 1; i <= len - 1; i++){
if(isScramble(s1.substring(0, i), s2.substring(0, i)) && isScramble(s1.substring(i), s2.substring(i)))
return true;
if (isScramble(s1.substring(0, i), s2.substring(len - i)) &&
isScramble(s1.substring(i), s2.substring(0, len - i)))
return true;
}
return false;
}
}
88. 合并两个有序数组
题目描述
给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 num1 成为一个有序数组。
说明:
- 初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。
- 你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。
示例:
输入: nums1 = [1,2,3,0,0,0], m = 3 nums2 = [2,5,6], n = 3 输出: [1,2,2,3,5,6]
解法
双指针解决。
Java
class Solution {
public void merge(int[] nums1, int m, int[] nums2, int n) {
int i = m - 1, j = n - 1;
int k = m + n - 1;
while (j >= 0) {
if (i >= 0 && nums1[i] >= nums2[j]) {
nums1[k--] = nums1[i--];
} else {
nums1[k--] = nums2[j--];
}
}
}
}
89. 格雷编码
题目描述
格雷编码是一个二进制数字系统,在该系统中,两个连续的数值仅有一个位数的差异。
给定一个代表编码总位数的非负整数 n,打印其格雷编码序列。格雷编码序列必须以 0 开头。
示例 1:
输入: 2 输出:[0,1,3,2]
解释: 00 - 0 01 - 1 11 - 3 10 - 2 对于给定的 n,其格雷编码序列并不唯一。 例如,[0,2,3,1]
也是一个有效的格雷编码序列。 00 - 0 10 - 2 11 - 3 01 - 1
示例 2:
输入: 0 输出:[0] 解释: 我们定义
格雷编码序列必须以 0 开头。给定
编码总位数为n 的格雷编码序列,其长度为 2n
。当 n = 0 时,长度为 20 = 1。 因此,当 n = 0 时,其格雷编码序列为 [0]。
解法
Java
class Solution {
public List<Integer> grayCode(int n) {
List<Integer> re = new ArrayList<>();
for (int i = 0; i < (1 << n); i++) re.add(i ^ (i >> 1));
return re;
}
}
90. 子集 II
题目描述
给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
示例:
输入: [1,2,2] 输出: [ [2], [1], [1,2,2], [2,2], [1,2], [] ]
解法
Java
class Solution {
public List<List<Integer>> subsetsWithDup(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
if(nums==null||nums.length==0) return res;
Arrays.sort(nums);
backTrack(res, nums, 0, new ArrayList<>());
return res;
}
private void backTrack(List<List<Integer>> res, int[] nums, int index, List<Integer> ls){
res.add(new ArrayList<>(ls));
if(index>=nums.length) return;
for(int i=index;i<nums.length;i++){
ls.add(nums[i]);
backTrack(res, nums, i+1, ls);
ls.remove(ls.size()-1);
while(i<nums.length-1 && nums[i+1]==nums[i]) i++;
}
}
}
91. 解码方法
题目描述
一条包含字母 A-Z
的消息通过以下方式进行了编码:
'A' -> 1 'B' -> 2 ... 'Z' -> 26
给定一个只包含数字的非空字符串,请计算解码方法的总数。
示例 1:
输入: "12" 输出: 2 解释: 它可以解码为 "AB"(1 2)或者 "L"(12)。
示例 2:
输入: "226" 输出: 3 解释: 它可以解码为 "BZ" (2 26), "VF" (22 6), 或者 "BBF" (2 2 6) 。
解法
Java
class Solution {
public int numDecodings(String s) {
int len = s.length();
if (len == 0) return 0;
int current = s.charAt(0) == '0' ? 0 : 1;
int last = 1;
for (int i = 1; i < len; i++) {
int tmp = current;
if(s.charAt(i) == '0'){
if(s.charAt(i-1) == '1' || s.charAt(i-1) == '2') current = last;
else return 0;
}else if(s.charAt(i-1) == '1' || s.charAt(i-1) == '2' && s.charAt(i) <= '6') {
current += last;
}
last = tmp;
}
return current;
}
}
92. 反转链表 II
题目描述
反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。
说明:
1 ≤ m ≤ n ≤ 链表长度。
示例:
输入: 1->2->3->4->5->NULL, m = 2, n = 4 输出: 1->4->3->2->5->NULL
解法
Java
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode reverseBetween(ListNode head, int m, int n) {
if (head == null || head.next == null || m == n) {
return head;
}
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode pre = dummy, cur = head;
for (int i = 0; i < m - 1; ++i) {
pre = cur;
cur = cur.next;
}
ListNode p = pre, q = cur;
for (int i = 0; i < n - m + 1; ++i) {
ListNode t = cur.next;
cur.next = pre;
pre = cur;
cur = t;
}
p.next = pre;
q.next = cur;
return dummy.next;
}
}
93. 复原 IP 地址
题目描述
给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式。
示例:
输入: "25525511135"
输出: ["255.255.11.135", "255.255.111.35"]
解法
Java
class Solution {
private List<String> result;
private int length;
public List<String> restoreIpAddresses(String s) {
result = new ArrayList<>();
length = s.length();
int[] ip = new int[4];
restoreIpAddresses(s,0,ip,0);
return result;
}
private void restoreIpAddresses(String s, int si, int[] ip, int pi) {
int sl = length - si , pl = 3 - pi , i = -1;
String pfx = null;
while (si< length){
int num = s.charAt(si++) - '1' + 1;
if (i==0) break;
i = i == -1 ? num : i * 10 + num;
sl--;
if (i>255) break;
if (sl < pl || sl > pl * 3) continue;
if (pi==3){
if (pfx==null){
StringBuilder pfxBuilder = new StringBuilder();
for (int j = 0; j < ip.length-1; j++) pfxBuilder.append(ip[j]).append('.');
pfx = pfxBuilder.toString();
}
result.add(pfx + i);
}
ip[pi] = i;
restoreIpAddresses(s,si,ip,pi+1);
}
}
}
94. 二叉树的中序遍历
题目描述
给定一个二叉树,返回它的中序 遍历。
示例:
输入: [1,null,2,3] 1 \ 2 / 3 输出: [1,3,2]
进阶: 递归算法很简单,你可以通过迭代算法完成吗?
解法
Java
// 递归版本
/*
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
inorderTraversal(root, list);
return list;
}
private void inorderTraversal(TreeNode root, List<Integer> list) {
if (root == null) {
return;
}
inorderTraversal(root.left, list);
list.add(root.val);
inorderTraversal(root.right, list);
}
}
*/
// 非递归版本
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
while (root != null || !stack.isEmpty()) {
while (root != null) {
stack.push(root);
root = root.left;
}
if (!stack.isEmpty()) {
root = stack.pop();
list.add(root.val);
root = root.right;
}
}
return list;
}
}
95. 不同的二叉搜索树 II
题目描述
给定一个整数 n,生成所有由 1 ... n 为节点所组成的二叉搜索树。
示例:
输入: 3 输出: [ [1,null,3,2], [3,2,null,1], [3,1,null,null,2], [2,1,3], [1,null,2,null,3] ] 解释: 以上的输出对应以下 5 种不同结构的二叉搜索树: 1 3 3 2 1 \ / / / \ \ 3 2 1 1 3 2 / / \ \ 2 1 2 3
解法
Java
class Solution {
public List<TreeNode> generateTrees(int n) {
if (n <= 0) return new ArrayList<>();
return generateTrees(1, n);
}
private List<TreeNode> generateTrees(int left, int right) {
List<TreeNode> list = new ArrayList<>();
if (left > right) {
list.add(null);
} else {
for (int i = left; i <= right; i++) {
List<TreeNode> leftTrees = generateTrees(left, i - 1);
List<TreeNode> rightTrees = generateTrees(i + 1, right);
for (TreeNode l : leftTrees) {
for (TreeNode r : rightTrees) {
TreeNode root = new TreeNode(i);
root.left = l;
root.right = r;
list.add(root);
}
}
}
}
return list;
}
}
96. 不同的二叉搜索树
题目描述
给定一个整数 n,求以 1 ... n 为节点组成的二叉搜索树有多少种?
示例:
输入: 3 输出: 5 解释: 给定 n = 3, 一共有 5 种不同结构的二叉搜索树: 1 3 3 2 1 \ / / / \ \ 3 2 1 1 3 2 / / \ \ 2 1 2 3
解法
Java
class Solution {
public int numTrees(int n) {
// res[n] 表示整数n组成的二叉搜索树个数
int[] res = new int[n + 1];
res[0] = 1;
for (int i = 1; i <= n; ++i) {
for (int j = 0; j < i; ++j) {
res[i] += res[j] * res[i - j - 1];
}
}
return res[n];
}
}
97. 交错字符串
题目描述
给定三个字符串 s1, s2, s3, 验证 s3 是否是由 s1 和 s2 交错组成的。
示例 1:
输入: s1 = "aabcc", s2 = "dbbca", s3 = "aadbbcbcac" 输出: true
示例 2:
输入: s1 = "aabcc", s2 = "dbbca", s3 = "aadbbbaccc" 输出: false
解法
Java
public class Solution {
public boolean isInterleave(String s1, String s2, String s3) {
char[] c1 = s1.toCharArray(), c2 = s2.toCharArray(), c3 = s3.toCharArray();
int m = s1.length(), n = s2.length();
if(m + n != s3.length()) return false;
return dfs(c1, c2, c3, 0, 0, 0, new boolean[m + 1][n + 1]);
}
private boolean dfs(char[] c1, char[] c2, char[] c3, int i, int j, int k, boolean[][] invalid) {
if(invalid[i][j]) return false;
if(k == c3.length) return true;
boolean valid = i < c1.length && c1[i] == c3[k] && dfs(c1, c2, c3, i + 1, j, k + 1, invalid) ||
j < c2.length && c2[j] == c3[k] && dfs(c1, c2, c3, i, j + 1, k + 1, invalid);
if(!valid) invalid[i][j] = true;
return valid;
}
}
98. 验证二叉搜索树
题目描述
给定一个二叉树,判断其是否是一个有效的二叉搜索树。
假设一个二叉搜索树具有如下特征:
- 节点的左子树只包含小于当前节点的数。
- 节点的右子树只包含大于当前节点的数。
- 所有左子树和右子树自身必须也是二叉搜索树。
示例 1:
输入: 2 / \ 1 3 输出: true
示例 2:
输入: 5 / \ 1 4 / \ 3 6 输出: false 解释: 输入为: [5,1,4,null,null,3,6]。 根节点的值为 5 ,但是其右子节点值为 4 。
解法
Java
class Solution {
private long current = Long.MIN_VALUE;
public boolean isValidBST(TreeNode root) {
if (root == null) return true;
if (isValidBST(root.left) && current < root.val) {
current = root.val;
return isValidBST(root.right);
}
return false;
}
}
99. 恢复二叉搜索树
题目描述
二叉搜索树中的两个节点被错误地交换。
请在不改变其结构的情况下,恢复这棵树。
示例 1:
输入: [1,3,null,null,2] 1 / 3 \ 2 输出: [3,1,null,null,2] 3 / 1 \ 2
示例 2:
输入: [3,1,4,null,null,2] 3 / \ 1 4 / 2 输出: [2,1,4,null,null,3] 2 / \ 1 4 / 3
进阶:
- 使用 O(n) 空间复杂度的解法很容易实现。
- 你能想出一个只使用常数空间的解决方案吗?
解法
Java
class Solution {
private TreeNode first,second,pre;
public void recoverTree(TreeNode root) {
traverse(root);
int temp = first.val;
first.val = second.val;
second.val = temp;
}
private void traverse(TreeNode root) {
if (root == null) return;
traverse(root.left);
if (pre != null) {
if (first == null && pre.val > root.val) first = pre;
if (first != null && pre.val > root.val) second = root;
}
pre = root;
traverse(root.right);
}
}
100. 相同的树
题目描述
给定两个二叉树,编写一个函数来检验它们是否相同。
如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。
示例 1:
输入: 1 1 / \ / \ 2 3 2 3 [1,2,3], [1,2,3] 输出: true
示例 2:
输入: 1 1 / \ 2 2 [1,2], [1,null,2] 输出: false
示例 3:
输入: 1 1 / \ / \ 2 1 1 2 [1,2,1], [1,1,2] 输出: false
解法
Java
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public boolean isSameTree(TreeNode p, TreeNode q) {
if (p == q) return true;
if (p == null || q == null || p.val != q.val) return false;
return isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
}
}
101. 对称二叉树
题目描述
给定一个二叉树,检查它是否是镜像对称的。
例如,二叉树 [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
说明:
如果你可以运用递归和迭代两种方法解决这个问题,会很加分。
解法
Java
/**
* 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 isSymmetric(root.left, root.right);
}
private boolean isSymmetric(TreeNode left, TreeNode right) {
if (left == null && right == null) return true;
if (left == null || right == null || left.val != right.val) return false;
return isSymmetric(left.left, right.right) && isSymmetric(left.right, right.left);
}
}
102. 二叉树的层次遍历
题目描述
给定一个二叉树,返回其按层次遍历的节点值。 (即逐层地,从左到右访问所有节点)。
例如:
给定二叉树: [3,9,20,null,null,15,7]
,
3 / \ 9 20 / \ 15 7
返回其层次遍历结果:
[ [3], [9,20], [15,7] ]
解法
队列实现。
Java
/**
* 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) {
if (root == null) return Collections.emptyList();
Deque<TreeNode> q = new ArrayDeque<>();
q.offer(root);
List<List<Integer>> res = new ArrayList<>();
while (!q.isEmpty()) {
int size = q.size();
List<Integer> t = new ArrayList<>();
while (size-- > 0) {
TreeNode node = q.poll();
t.add(node.val);
if (node.left != null) q.offer(node.left);
if (node.right != null) q.offer(node.right);
}
res.add(t);
}
return res;
}
}
103. 二叉树的锯齿形层次遍历
题目描述
给定一个二叉树,返回其节点值的锯齿形层次遍历。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。
例如:
给定二叉树 [3,9,20,null,null,15,7]
,
3 / \ 9 20 / \ 15 7
返回锯齿形层次遍历如下:
[ [3], [20,9], [15,7] ]
解法
Java
class Solution {
private List<List<Integer>> list;
public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
list = new ArrayList<>();
lever(root, 0);
for (int i = 1; i < list.size(); i = i + 2) {
List<Integer> nList = list.get(i);
List<Integer> nnl = new ArrayList<>();
for (int j = nList.size() - 1; j >= 0; j--) nnl.add(nList.get(j));
list.set(i, nnl);
}
return list;
}
private void lever(TreeNode root, int l) {
if (root == null) return;
while (l > list.size() - 1) list.add(new ArrayList<>());
list.get(l++).add(root.val);
lever(root.left, l);
lever(root.right, l);
}
}
104. 二叉树的最大深度
题目描述
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。
示例:
给定二叉树 [3,9,20,null,null,15,7]
,
3 / \ 9 20 / \ 15 7
返回它的最大深度 3 。
解法
递归遍历左右子树,求左右子树的最大深度 +1 即可。
Java
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public int maxDepth(TreeNode root) {
if (root == null) return 0;
int l = maxDepth(root.left);
int r = maxDepth(root.right);
return 1 + Math.max(l, r);
}
}
105. 从前序与中序遍历序列构造二叉树
题目描述
根据一棵树的前序遍历与中序遍历构造二叉树。
注意:
你可以假设树中没有重复的元素。
例如,给出
前序遍历 preorder = [3,9,20,15,7] 中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树:
3 / \ 9 20 / \ 15 7
解法
先遍历前序节点,对于前序的根节点,在中序节点 [i1, i2]
中找到根节点的位置 pos,就可以将中序节点分成:左子树 [i1, pos - 1]
、右子树 [pos + 1, i2]
。
通过左右子树的区间,可以计算出左、右子树节点的个数,假设为 m、n。然后在前序节点中,从根节点往后的 m 个节点为左子树,再往后的 n 个节点为右子树。
递归求解即可。
前序遍历:先遍历根节点,再遍历左右子树;中序遍历:先遍历左子树,再遍历根节点,最后遍历右子树。
Java
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
private Map<Integer, Integer> indexes = new HashMap<>();
public TreeNode buildTree(int[] preorder, int[] inorder) {
int n = inorder.length;
for (int i = 0; i < n; ++i) {
indexes.put(inorder[i], i);
}
return build(preorder, inorder, 0, n - 1, 0, n - 1);
}
private TreeNode build(int[] preorder, int[] inorder, int p1, int p2, int i1, int i2) {
if (p1 > p2 || i1 > i2) return null;
int rootVal = preorder[p1];
int pos = indexes.get(rootVal);
TreeNode node = new TreeNode(rootVal);
// pos==i1,说明只有右子树,左子树为空
node.left = pos == i1 ? null : build(preorder, inorder, p1 + 1, pos - i1 + p1, i1, pos - 1);
// pos==i2,说明只有左子树,右子树为空
node.right = pos == i2 ? null : build(preorder, inorder, pos - i1 + p1 + 1, p2, pos + 1, i2);
return node;
}
}
106. 从中序与后序遍历序列构造二叉树
题目描述
根据一棵树的中序遍历与后序遍历构造二叉树。
注意:
你可以假设树中没有重复的元素。
例如,给出
中序遍历 inorder = [9,3,15,20,7] 后序遍历 postorder = [9,15,7,20,3]
返回如下的二叉树:
3 / \ 9 20 / \ 15 7
解法
思路同 105。
Java
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
private Map<Integer, Integer> indexes = new HashMap<>();
public TreeNode buildTree(int[] inorder, int[] postorder) {
int n = inorder.length;
for (int i = 0; i < n; ++i) {
indexes.put(inorder[i], i);
}
return build(inorder, postorder, 0, inorder.length - 1, 0, postorder.length - 1);
}
private TreeNode build(int[] inorder, int[] postorder, int i1, int i2, int p1, int p2) {
if (i1 > i2 || p1 > p2) return null;
int rootVal = postorder[p2];
int pos = indexes.get(rootVal);
TreeNode root = new TreeNode(rootVal);
root.left = pos == i1 ? null : build(inorder, postorder, i1, pos - 1, p1, p1 - i1 + pos - 1);
root.right = pos == i2 ? null : build(inorder, postorder, pos + 1, i2, p1 - i1 + pos, p2 - 1);
return root;
}
}
107. 二叉树的层次遍历 II
题目描述
给定一个二叉树,返回其节点值自底向上的层次遍历。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)
例如:
给定二叉树 [3,9,20,null,null,15,7]
,
3 / \ 9 20 / \ 15 7
返回其自底向上的层次遍历为:
[ [15,7], [9,20], [3] ]
解法
同 102,最后反转一下结果即可。
Java
/**
* 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>> levelOrderBottom(TreeNode root) {
if (root == null) return Collections.emptyList();
Deque<TreeNode> q = new ArrayDeque<>();
List<List<Integer>> res = new ArrayList<>();
q.offer(root);
while (!q.isEmpty()) {
int size = q.size();
List<Integer> t = new ArrayList<>();
while (size-- > 0) {
TreeNode node = q.poll();
t.add(node.val);
if (node.left != null) q.offer(node.left);
if (node.right != null) q.offer(node.right);
}
res.add(0, t);
}
return res;
}
}
108. 将有序数组转换为二叉搜索树
题目描述
将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。
本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。
示例:
给定有序数组: [-10,-3,0,5,9], 一个可能的答案是:[0,-3,9,-10,null,5],它可以表示下面这个高度平衡二叉搜索树: 0 / \ -3 9 / / -10 5
解法
Java
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public TreeNode sortedArrayToBST(int[] nums) {
return sortedArrayToBST(nums, 0, nums.length - 1);
}
private TreeNode sortedArrayToBST(int[] nums, int l, int r) {
if (l > r) {
return null;
}
int mid = (l + r) >> 1;
TreeNode root = new TreeNode(nums[mid]);
root.left = sortedArrayToBST(nums, l, mid - 1);
root.right = sortedArrayToBST(nums, mid + 1, r);
return root;
}
}
109. 有序链表转换二叉搜索树
题目描述
给定一个单链表,其中的元素按升序排序,将其转换为高度平衡的二叉搜索树。
本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。
示例:
给定的有序链表: [-10, -3, 0, 5, 9], 一个可能的答案是:[0, -3, 9, -10, null, 5], 它可以表示下面这个高度平衡二叉搜索树: 0 / \ -3 9 / / -10 5
解法
Java
class Solution {
public TreeNode sortedListToBST(ListNode head) {
if(head==null) return null;
if(head.next==null) return new TreeNode(head.val);
ListNode slow = head;
ListNode fast = head;
ListNode prev = null;
while(fast!=null && fast.next!=null){
prev = slow;
slow=slow.next;
fast=fast.next.next;
}
TreeNode root = new TreeNode(Objects.requireNonNull(prev).next.val);
prev.next = null;
root.left = sortedListToBST(head);
root.right = sortedListToBST(slow.next);
return root;
}
}
110. 平衡二叉树
题目描述
给定一个二叉树,判断它是否是高度平衡的二叉树。
本题中,一棵高度平衡二叉树定义为:
一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过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
。
解法
Java
class Solution {
public boolean isBalanced(TreeNode root) {
return depth(root) != -1;
}
private int depth(TreeNode root) {
if (root == null) return 0;
int left = depth(root.left);
if (left == -1) return -1;
int right = depth(root.right);
if (right == -1 || Math.abs(left - right) > 1) return -1;
return Math.max(left, right) + 1;
}
}
111. 二叉树的最小深度
题目描述
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
说明: 叶子节点是指没有子节点的节点。
示例:
给定二叉树 [3,9,20,null,null,15,7]
,
3 / \ 9 20 / \ 15 7
返回它的最小深度 2.
解法
Java
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public int minDepth(TreeNode root) {
if (root == null) return 0;
if (root.left == null && root.right == null) return 1;
int l = minDepth(root.left);
int r = minDepth(root.right);
if (root.left == null || root.right == null) return l + r + 1;
return Math.min(l, r) + 1;
}
}
112. 路径总和
题目描述
给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。
说明: 叶子节点是指没有子节点的节点。
示例:
给定如下二叉树,以及目标和 sum = 22
,
5 / \ 4 8 / / \ 11 13 4 / \ \ 7 2 1
返回 true
, 因为存在目标和为 22 的根节点到叶子节点的路径 5->4->11->2
。
解法
递归求解,递归地询问它的子节点是否能满足条件即可。
Java
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public boolean hasPathSum(TreeNode root, int sum) {
return dfs(root, sum);
}
private boolean dfs(TreeNode root, int sum) {
if (root == null) return false;
if (root.val == sum && root.left == null && root.right == null) return true;
return dfs(root.left, sum - root.val) || dfs(root.right, sum - root.val);
}
}
113. 路径总和 II
题目描述
给定一个二叉树和一个目标和,找到所有从根节点到叶子节点路径总和等于给定目标和的路径。
说明: 叶子节点是指没有子节点的节点。
示例:
给定如下二叉树,以及目标和 sum = 22
,
5 / \ 4 8 / / \ 11 13 4 / \ / \ 7 2 5 1
返回:
[ [5,4,11,2], [5,8,4,5] ]
解法
深度优先搜索+路径记录。
Java
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
private List<List<Integer>> res;
private List<Integer> path;
public List<List<Integer>> pathSum(TreeNode root, int sum) {
if (root == null) return Collections.emptyList();
res = new ArrayList<>();
path = new ArrayList<>();
dfs(root, sum);
return res;
}
private void dfs(TreeNode root, int sum) {
if (root == null) return;
path.add(root.val);
if (root.val == sum && root.left == null && root.right == null) {
res.add(new ArrayList<>(path));
}
dfs(root.left, sum - root.val);
dfs(root.right, sum - root.val);
path.remove(path.size() - 1);
}
}
114. 二叉树展开为链表
题目描述
给定一个二叉树,原地将它展开为链表。
例如,给定二叉树
1 / \ 2 5 / \ \ 3 4 6
将其展开为:
1 \ 2 \ 3 \ 4 \ 5 \ 6
解法
Java
class Solution {
public void flatten(TreeNode root) {
if (root==null) return;
TreeNode right = root.right;
flatten(right);
flatten(root.left);
root.right = root.left;
root.left = null;
TreeNode cache = root;
while (cache.right!=null) cache = cache.right;
cache.right = right;
}
}
115. 不同的子序列
题目描述
给定一个字符串 S 和一个字符串 T,计算在 S 的子序列中 T 出现的个数。
一个字符串的一个子序列是指,通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置所组成的新字符串。(例如,"ACE"
是 "ABCDE"
的一个子序列,而 "AEC"
不是)
示例 1:
输入: S ="rabbbit"
, T ="rabbit" 输出: 3
解释: 如下图所示, 有 3 种可以从 S 中得到"rabbit" 的方案
。 (上箭头符号 ^ 表示选取的字母)rabbbit
^^^^ ^^rabbbit
^^ ^^^^rabbbit
^^^ ^^^
示例 2:
输入: S ="babgbag"
, T ="bag" 输出: 5
解释: 如下图所示, 有 5 种可以从 S 中得到"bag" 的方案
。 (上箭头符号 ^ 表示选取的字母)babgbag
^^ ^babgbag
^^ ^babgbag
^ ^^babgbag
^ ^^babgbag
^^^
解法
Java
class Solution {
public int numDistinct(String s, String t) {
int[][] hash = new int[256][t.length() + 1];
int[] cnt = new int[t.length() + 1];
cnt[0] = 1;
for (int i = 0; i < t.length();) {
char c = t.charAt(i);
hash[c][++hash[c][0]] = ++i;
}
for(char c : s.toCharArray()) {
for(int i = hash[c][0]; i > 0; i--) {
cnt[hash[c][i]] += cnt[hash[c][i] - 1];
}
}
return cnt[t.length()];
}
}
116. 填充每个节点的下一个右侧节点指针
题目描述
给定一个完美二叉树,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下:
struct Node { int val; Node *left; Node *right; Node *next; }
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL
。
初始状态下,所有 next 指针都被设置为 NULL
。
示例:
输入:{"$id":"1","left":{"$id":"2","left":{"$id":"3","left":null,"next":null,"right":null,"val":4},"next":null,"right":{"$id":"4","left":null,"next":null,"right":null,"val":5},"val":2},"next":null,"right":{"$id":"5","left":{"$id":"6","left":null,"next":null,"right":null,"val":6},"next":null,"right":{"$id":"7","left":null,"next":null,"right":null,"val":7},"val":3},"val":1} 输出:{"$id":"1","left":{"$id":"2","left":{"$id":"3","left":null,"next":{"$id":"4","left":null,"next":{"$id":"5","left":null,"next":{"$id":"6","left":null,"next":null,"right":null,"val":7},"right":null,"val":6},"right":null,"val":5},"right":null,"val":4},"next":{"$id":"7","left":{"$ref":"5"},"next":null,"right":{"$ref":"6"},"val":3},"right":{"$ref":"4"},"val":2},"next":null,"right":{"$ref":"7"},"val":1} 解释:给定二叉树如图 A 所示,你的函数应该填充它的每个 next 指针,以指向其下一个右侧节点,如图 B 所示。
提示:
- 你只能使用常量级额外空间。
- 使用递归解题也符合要求,本题中递归程序占用的栈空间不算做额外的空间复杂度。
解法
Java
class Solution {
public void connect(TreeLinkNode root) {
if (root == null || root.left == null) return;
root.left.next = root.right;
if (root.next == null) root.right.next = null;
else root.right.next = root.next.left;
connect(root.left);
connect(root.right);
}
}
117. 填充每个节点的下一个右侧节点指针 II
题目描述
给定一个二叉树
struct Node { int val; Node *left; Node *right; Node *next; }
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL
。
初始状态下,所有 next 指针都被设置为 NULL
。
进阶:
- 你只能使用常量级额外空间。
- 使用递归解题也符合要求,本题中递归程序占用的栈空间不算做额外的空间复杂度。
示例:
输入:root = [1,2,3,4,5,null,7] 输出:[1,#,2,3,#,4,5,7,#] 解释:给定二叉树如图 A 所示,你的函数应该填充它的每个 next 指针,以指向其下一个右侧节点,如图 B 所示。
提示:
- 树中的节点数小于
6000
-100 <= node.val <= 100
解法
Java
public class Solution {
public void connect(TreeLinkNode root) {
if (root == null) return;
TreeLinkNode first_node_next_layer = null;
TreeLinkNode preNode = null;
for (TreeLinkNode curNode = root; curNode != null; curNode = curNode.next) {
if (curNode.left != null) {
if (preNode == null) {
preNode = curNode.left;
first_node_next_layer = curNode.left;
} else {
preNode.next = curNode.left;
preNode = preNode.next;
}
}
if (curNode.right != null) {
if (preNode == null) {
preNode = curNode.right;
first_node_next_layer = curNode.right;
} else {
preNode.next = curNode.right;
preNode = preNode.next;
}
}
}
connect(first_node_next_layer);
}
}
118. 杨辉三角
题目描述
给定一个非负整数 numRows,生成杨辉三角的前 numRows 行。
在杨辉三角中,每个数是它左上方和右上方的数的和。
示例:
输入: 5 输出: [ [1], [1,1], [1,2,1], [1,3,3,1], [1,4,6,4,1] ]
解法
先设置每一行首尾元素为 1,其它元素为 0。然后根据杨辉三角,设置每一行其它元素即可。
Java
class Solution {
public List<List<Integer>> generate(int numRows) {
List<List<Integer>> res = new ArrayList<>();
if (numRows == 0) return res;
for (int i = 0; i < numRows; ++i) {
// 每一行
List<Integer> t = new ArrayList<>();
for (int j = 0; j < i + 1; ++j) {
boolean firstOrLast = j == 0 || j == i;
// 设置每一行首尾元素为1,其它元素为0
t.add(firstOrLast ? 1 : 0);
}
for (int j = 1; j < i; ++j) {
int val = res.get(i - 1).get(j - 1) + res.get(i - 1).get(j);
t.set(j, val);
}
res.add(t);
}
return res;
}
}
119. 杨辉三角 II
题目描述
给定一个非负索引 k,其中 k ≤ 33,返回杨辉三角的第 k 行。
在杨辉三角中,每个数是它左上方和右上方的数的和。
示例:
输入: 3 输出: [1,3,3,1]
进阶:
你可以优化你的算法到 O(k) 空间复杂度吗?
解法
Java
class Solution {
public List<Integer> getRow(int rowIndex) {
List<Integer> ret = new LinkedList<>();
long nk = 1;
for (int i = 0; i <= rowIndex; i++) {
ret.add((int) nk);
nk = nk * (rowIndex - i) / (i + 1);
}
return ret;
}
}
120. 三角形最小路径和
题目描述
给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。
例如,给定三角形:
[ [2], [3,4], [6,5,7], [4,1,8,3] ]
自顶向下的最小路径和为 11
(即,2 + 3 + 5 + 1 = 11)。
说明:
如果你可以只使用 O(n) 的额外空间(n 为三角形的总行数)来解决这个问题,那么你的算法会很加分。
解法
Java
class Solution {
private int[][] cache = null;
public int minimumTotal(List<List<Integer>> triangle) {
int n = triangle.size();
cache = new int[n][triangle.get(n - 1).size()];
for (int[] row : cache) Arrays.fill(row, -1);
return dfs(triangle,0,0);
}
private int dfs(List<List<Integer>> triangle, int row, int col) {
if (row == triangle.size()) return 0;
if (cache[row][col] != -1) return cache[row][col];
int l = dfs(triangle,row+1,col);
int r = dfs(triangle,row+1,col+1);
int res = Math.min(l,r)+triangle.get(row).get(col);
cache[row][col] = res;
return res;
}
}
121. 买卖股票的最佳时机
题目描述
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
如果你最多只允许完成一笔交易(即买入和卖出一支股票一次),设计一个算法来计算你所能获取的最大利润。
注意你不能在买入股票前卖出股票。
示例 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。
解法
Java
class Solution {
public int maxProfit(int[] prices) {
if (prices == null) return 0;
int res = 0;
int min = Integer.MAX_VALUE;
for (int price : prices) {
min = Math.min(min, price);
res = Math.max(res, price - min);
}
return res;
}
}
122. 买卖股票的最佳时机 II
题目描述
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例 1:
输入: [7,1,5,3,6,4] 输出: 7 解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。 随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
示例 2:
输入: [1,2,3,4,5] 输出: 4 解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。 注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。 因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。
示例 3:
输入: [7,6,4,3,1] 输出: 0 解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
解法
所有上涨交易日都做买卖,所有下跌交易日都不做买卖,这样便能实现利润最大化。
Java
class Solution {
public int maxProfit(int[] prices) {
if (prices == null) return 0;
int res = 0;
for (int i = 1, n = prices.length; i < n; ++i) {
// 策略是所有上涨交易日都做买卖,所以下跌交易日都不做买卖
int t = prices[i] - prices[i - 1];
res += Math.max(t, 0);
}
return res;
}
}
123. 买卖股票的最佳时机 III
题目描述
给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。
注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例 1:
输入: [3,3,5,0,0,3,1,4] 输出: 6 解释: 在第 4 天(股票价格 = 0)的时候买入,在第 6 天(股票价格 = 3)的时候卖出,这笔交易所能获得利润 = 3-0 = 3 。 随后,在第 7 天(股票价格 = 1)的时候买入,在第 8 天 (股票价格 = 4)的时候卖出,这笔交易所能获得利润 = 4-1 = 3 。
示例 2:
输入: [1,2,3,4,5] 输出: 4 解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。 注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。 因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。
示例 3:
输入: [7,6,4,3,1] 输出: 0 解释: 在这个情况下, 没有交易完成, 所以最大利润为 0。
解法
Java
class Solution {
public int maxProfit(int[] prices) {
if (prices.length <= 1) return 0;
int m = 2 , n = prices.length;
int[][] dp = new int[m+1][n];
for (int i = 1; i <= m; i++) {
int maxdiff = Integer.MIN_VALUE;
for (int j = 1; j < n; j++) {
maxdiff = Math.max(maxdiff, dp[i-1][j-1] - prices[j-1]);
dp[i][j] = Math.max(dp[i][j-1], prices[j] + maxdiff);
}
}
return dp[m][n-1];
}
}
124. 二叉树中的最大路径和
题目描述
给定一个非空二叉树,返回其最大路径和。
本题中,路径被定义为一条从树中任意节点出发,达到任意节点的序列。该路径至少包含一个节点,且不一定经过根节点。
示例 1:
输入: [1,2,3] 1 / \ 2 3 输出: 6
示例 2:
输入: [-10,9,20,null,null,15,7] -10 / \ 9 20 / \ 15 7 输出: 42
解法
Java
class Solution {
private int val = Integer.MIN_VALUE;
public int maxPathSum(TreeNode root) {
dfs(root);
return val;
}
public int dfs(TreeNode root) {
if (root == null) {
return 0;
}
int left = Math.max(0, dfs(root.left));
int right = Math.max(0, dfs(root.right));
int val1 = root.val + left + right;
int val2 = root.val + Math.max(0, Math.max(left, right));
val = Math.max(val, val1);
return val2;
}
}
125. 验证回文串
题目描述
给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。
说明:本题中,我们将空字符串定义为有效的回文串。
示例 1:
输入: "A man, a plan, a canal: Panama" 输出: true
示例 2:
输入: "race a car" 输出: false
解法
Java
class Solution {
public boolean isPalindrome(String s) {
int i = 0;
int j = s.length() - 1;
while (i < j) {
while (i < j && !Character.isLetterOrDigit(s.charAt(i))) {
i++;
}
while (i < j && !Character.isLetterOrDigit(s.charAt(j))) {
j--;
}
if (i < j && Character.toUpperCase(s.charAt(i)) != Character.toUpperCase(s.charAt(j))) {
return false;
}
i++;
j--;
}
return true;
}
}
126. 单词接龙 II
题目描述
给定两个单词(beginWord 和 endWord)和一个字典 wordList,找出所有从 beginWord 到 endWord 的最短转换序列。转换需遵循如下规则:
- 每次转换只能改变一个字母。
- 转换过程中的中间单词必须是字典中的单词。
说明:
- 如果不存在这样的转换序列,返回一个空列表。
- 所有单词具有相同的长度。
- 所有单词只由小写字母组成。
- 字典中不存在重复的单词。
- 你可以假设 beginWord 和 endWord 是非空的,且二者不相同。
示例 1:
输入: beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"] 输出: [ ["hit","hot","dot","dog","cog"], ["hit","hot","lot","log","cog"] ]
示例 2:
输入: beginWord = "hit" endWord = "cog" wordList = ["hot","dot","dog","lot","log"] 输出: [] 解释: endWord "cog" 不在字典中,所以不存在符合要求的转换序列。
解法
Java
class Solution {
private boolean isConnected = false;
private Map<String, List<String>> hs;
public List<List<String>> findLadders(String beginWord, String endWord, List<String> wordList) {
hs = new HashMap<>(16);
List<List<String>> result = new ArrayList<>();
if(!wordList.contains(endWord))
return result;
HashSet<String> dict = new HashSet<>(wordList);
Set<String> fwd = new HashSet<>();
fwd.add(beginWord);
Set<String> bwd = new HashSet<>();
bwd.add(endWord);
bfs(fwd, bwd, dict, false);
if(!isConnected) return result;
List<String> temp = new ArrayList<>();
temp.add(beginWord);
dfs(result, temp, beginWord, endWord);
return result;
}
private void bfs(Set<String> forward, Set<String> backward, Set<String> dict, boolean swap){
if(forward.isEmpty() || backward.isEmpty()) return;
if(forward.size() > backward.size()){
bfs(backward, forward, dict, !swap);
return;
}
dict.removeAll(forward);
dict.removeAll(backward);
Set<String> set3 = new HashSet<>();
for(String str : forward)
for (int i = 0; i < str.length(); i++) {
char[] ary = str.toCharArray();
for (char j = 'a'; j <= 'z'; j++) {
ary[i] = j;
String temp = new String(ary);
if (!backward.contains(temp) && !dict.contains(temp)) continue;
String key = !swap ? str : temp;
String val = !swap ? temp : str;
if (!hs.containsKey(key)) hs.put(key, new ArrayList<>());
if (backward.contains(temp)) {
hs.get(key).add(val);
isConnected = true;
}
if (!isConnected && dict.contains(temp)) {
hs.get(key).add(val);
set3.add(temp);
}
}
}
if(!isConnected) bfs(set3, backward, dict, swap);
}
private void dfs(List<List<String>> result, List<String> temp, String start, String end){
if(start.equals(end)){
result.add(new ArrayList<>(temp));
return;
}
if(!hs.containsKey(start)) return;
for(String s : hs.get(start)){
temp.add(s);
dfs(result, temp, s, end);
temp.remove(temp.size()-1);
}
}
}
127. 单词接龙
题目描述
给定两个单词(beginWord 和 endWord)和一个字典,找到从 beginWord 到 endWord 的最短转换序列的长度。转换需遵循如下规则:
- 每次转换只能改变一个字母。
- 转换过程中的中间单词必须是字典中的单词。
说明:
- 如果不存在这样的转换序列,返回 0。
- 所有单词具有相同的长度。
- 所有单词只由小写字母组成。
- 字典中不存在重复的单词。
- 你可以假设 beginWord 和 endWord 是非空的,且二者不相同。
示例 1:
输入: beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"] 输出: 5 解释: 一个最短转换序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog", 返回它的长度 5。
示例 2:
输入: beginWord = "hit" endWord = "cog" wordList = ["hot","dot","dog","lot","log"] 输出: 0 解释: endWord "cog" 不在字典中,所以无法进行转换。
解法
Java
class Solution {
public int ladderLength(String beginWord, String endWord, List<String> wordList) {
Queue<String> queue = new LinkedList<>();
// 需要转hashSet
Set<String> wordSet = new HashSet<>(wordList);
queue.offer(beginWord);
int level = 1;
int curNum = 1;
int nextNum = 0;
while (!queue.isEmpty()) {
String s = queue.poll();
--curNum;
char[] chars = s.toCharArray();
for (int i = 0; i < chars.length; ++i) {
char ch = chars[i];
for (char j = 'a'; j <= 'z'; ++j) {
chars[i] = j;
String tmp = new String(chars);
// 字典中包含生成的中间字符串
if (wordSet.contains(tmp)) {
// 中间字符串与 endWord 相等
if (endWord.equals(tmp)) {
return level + 1;
}
// 中间字符串不是 endWord,则入队
queue.offer(tmp);
++nextNum;
// 确保之后不会再保存 tmp 字符串
wordSet.remove(tmp);
}
}
chars[i] = ch;
}
if (curNum == 0) {
curNum = nextNum;
nextNum = 0;
++level;
}
}
return 0;
}
}
128. 最长连续序列
题目描述
给定一个未排序的整数数组,找出最长连续序列的长度。
要求算法的时间复杂度为 O(n)。
示例:
输入: [100, 4, 200, 1, 3, 2]
输出: 4
解释: 最长连续序列是 [1, 2, 3, 4]。它的长度为 4。
解法
Java
class Solution {
public int longestConsecutive(int[] nums) {
if (nums.length == 0) return 0;
Arrays.sort(nums);
int start = 0, casch = nums[0], longest = 0;
for (int i = 1; i < nums.length; i++) {
int nc = nums[i] , con = nc - casch;
if (con == 0) {
start++;
} else if (con != 1) {
longest = Math.max(i - start, longest);
start = i;
}
casch = nc;
}
return Math.max(nums.length - start, longest);
}
}
129. 求根到叶子节点数字之和
题目描述
给定一个二叉树,它的每个结点都存放一个 0-9
的数字,每条从根到叶子节点的路径都代表一个数字。
例如,从根到叶子节点路径 1->2->3
代表数字 123
。
计算从根到叶子节点生成的所有数字之和。
说明: 叶子节点是指没有子节点的节点。
示例 1:
输入: [1,2,3] 1 / \ 2 3 输出: 25 解释: 从根到叶子节点路径1->2
代表数字12
. 从根到叶子节点路径1->3
代表数字13
. 因此,数字总和 = 12 + 13 =25
.
示例 2:
输入: [4,9,0,5,1] 4 / \ 9 0 / \ 5 1 输出: 1026 解释: 从根到叶子节点路径4->9->5
代表数字 495. 从根到叶子节点路径4->9->1
代表数字 491. 从根到叶子节点路径4->0
代表数字 40. 因此,数字总和 = 495 + 491 + 40 =1026
.
解法
Java
class Solution {
public int sumNumbers(TreeNode root) {
return sumNumbers(root,0);
}
private int sumNumbers(TreeNode root, int sum) {
if (root==null) return 0;
sum = sum *10 + root.val;
if (root.left==null && root.right==null) return sum;
return sumNumbers(root.left,sum)+sumNumbers(root.right,sum);
}
}
130. 被围绕的区域
题目描述
给定一个二维的矩阵,包含 'X'
和 'O'
(字母 O)。
找到所有被 'X'
围绕的区域,并将这些区域里所有的 'O'
用 'X'
填充。
示例:
X X X X X O O X X X O X X O X X
运行你的函数后,矩阵变为:
X X X X X X X X X X X X X O X X
解释:
被围绕的区间不会存在于边界上,换句话说,任何边界上的 'O'
都不会被填充为 'X'
。 任何不在边界上,或不与边界上的 'O'
相连的 'O'
最终都会被填充为 'X'
。如果两个元素在水平或垂直方向相邻,则称它们是“相连”的。
解法
Java
class Solution {
/**
* 坐标点
*/
private class Point {
int x;
int y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
}
public void solve(char[][] board) {
if (board == null || board.length < 3 || board[0].length < 3) {
return;
}
int m = board.length;
int n = board[0].length;
// top & bottom
for (int i = 0; i < n; ++i) {
bfs(board, 0, i);
bfs(board, m - 1, i);
}
// left & right
for (int i = 1; i < m - 1; ++i) {
bfs(board, i, 0);
bfs(board, i, n - 1);
}
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (board[i][j] == 'O') {
board[i][j] = 'X';
} else if (board[i][j] == 'Y') {
board[i][j] = 'O';
}
}
}
}
/**
* 广度优先搜索
* @param board
* @param i
* @param j
*/
private void bfs(char[][] board, int i, int j) {
Queue<Point> queue = new LinkedList<>();
if (isValid(board, i, j)) {
// 遇到'O',修改为'Y'
board[i][j] = 'Y';
queue.offer(new Point(i, j));
}
while (!queue.isEmpty()) {
Point p = queue.poll();
// 获取下一层所有有效坐标点,并加入队列
List<Point> points = getNextLevelValidPoints(board, p.x, p.y);
for (Point point : points) {
queue.offer(point);
}
}
}
/**
* 获取下一层所有有效坐标点,将这些坐标点修改为 'Y' 并返回
* @param board
* @param i
* @param j
* @return list
*/
private List<Point> getNextLevelValidPoints(char[][] board, int i, int j) {
List<Point> points = new ArrayList<>();
Point[] arr = new Point[] { new Point(i - 1, j), new Point(i + 1, j), new Point(i, j - 1),
new Point(i, j + 1) };
for (Point point : arr) {
if (isValid(board, point.x, point.y)) {
board[point.x][point.y] = 'Y';
points.add(point);
}
}
return points;
}
/**
* 判断坐标是否有效
* @param board
* @param i
* @param j
* @return boolean
*/
private boolean isValid(char[][] board, int i, int j) {
int m = board.length;
int n = board[0].length;
// 当前坐标对应的值是'O',才算有效
return i >= 0 && i <= m - 1 && j >= 0 && j <= n - 1 && board[i][j] == 'O';
}
}
131. 分割回文串
题目描述
给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。
返回 s 所有可能的分割方案。
示例:
输入: "aab" 输出: [ ["aa","b"], ["a","a","b"] ]
解法
Java
class Solution {
private List<List<String>> res;
public List<List<String>> partition(String s) {
res= new ArrayList<>();
func(new ArrayList<>(),0,s);
return res;
}
private void func(List<String> temp, int start, String str){
if(start>=str.length()){
res.add(new ArrayList<>(temp));
return;
}
int ed=str.indexOf(str.charAt(start),start+1);
while(ed>0){
int s=start;
int e=ed;
boolean flag=false;
while(s<e){
if(str.charAt(s)==str.charAt(e)){
s++;
e--;
} else{
flag=true;
break;
}
}
if(!flag){
temp.add(str.substring(start,ed+1));
func(temp,ed+1,str);
temp.remove(temp.size()-1);
}
ed=str.indexOf(str.charAt(start),ed+1);
}
temp.add(str.substring(start,start+1));
func(temp,start+1,str);
temp.remove(temp.size()-1);
}
}
132. 分割回文串 II
题目描述
给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。
返回符合要求的最少分割次数。
示例:
输入: "aab" 输出: 1 解释: 进行一次分割就可将 s 分割成 ["aa","b"] 这样两个回文子串。
解法
Java
class Solution {
public int minCut(String s) {
if(s==null || s.length()<=1)return 0;
int len = s.length();
int[] dp = new int[len];
for(int i=0;i<len;i++) dp[i] = len - 1;
for(int i=0;i<len;i++){
mincutHelper(s , i , i , dp);
mincutHelper(s, i , i+1 , dp);
}
return dp[len-1];
}
private void mincutHelper(String s, int i, int j, int[] dp){
int len = s.length();
while(i>=0 && j<len && s.charAt(i)==s.charAt(j)){
dp[j] = Math.min(dp[j] , (i==0?-1:dp[i-1])+1);
i--;
j++;
}
}
}
133. 克隆图
题目描述
给你无向 连通 图中一个节点的引用,请你返回该图的 深拷贝(克隆)。
图中的每个节点都包含它的值 val
(int
) 和其邻居的列表(list[Node]
)。
class Node { public int val; public List<Node> neighbors; }
测试用例格式:
简单起见,每个节点的值都和它的索引相同。例如,第一个节点值为 1,第二个节点值为 2,以此类推。该图在测试用例中使用邻接列表表示。
邻接列表是用于表示有限图的无序列表的集合。每个列表都描述了图中节点的邻居集。
给定节点将始终是图中的第一个节点(值为 1)。你必须将 给定节点的拷贝 作为对克隆图的引用返回。
示例 1:
输入:adjList = [[2,4],[1,3],[2,4],[1,3]] 输出:[[2,4],[1,3],[2,4],[1,3]] 解释: 图中有 4 个节点。 节点 1 的值是 1,它有两个邻居:节点 2 和 4 。 节点 2 的值是 2,它有两个邻居:节点 1 和 3 。 节点 3 的值是 3,它有两个邻居:节点 2 和 4 。 节点 4 的值是 4,它有两个邻居:节点 1 和 3 。
示例 2:
输入:adjList = [[]] 输出:[[]] 解释:输入包含一个空列表。该图仅仅只有一个值为 1 的节点,它没有任何邻居。
示例 3:
输入:adjList = [] 输出:[] 解释:这个图是空的,它不含任何节点。
示例 4:
输入:adjList = [[2],[1]] 输出:[[2],[1]]
提示:
- 节点数介于 1 到 100 之间。
- 每个节点值都是唯一的。
- 无向图是一个简单图,这意味着图中没有重复的边,也没有自环。
- 由于图是无向的,如果节点 p 是节点 q 的邻居,那么节点 q 也必须是节点 p 的邻居。
- 图是连通图,你可以从给定节点访问到所有节点。
解法
Java
class Solution {
private Map<Node, Node> cache;
public Node cloneGraph(Node node) {
cache = new HashMap<>(16);
return helper(node);
}
private Node helper(Node node) {
if (node == null) return null;
else if (cache.containsKey(node)) return cache.get(node);
Node nodeCopy = new Node(node.val,new ArrayList<>());
cache.put(node, nodeCopy);
for (Node neighbor : node.neighbors) nodeCopy.neighbors.add(helper(neighbor));
return nodeCopy;
}
}
134. 加油站
题目描述
在一条环路上有 N 个加油站,其中第 i 个加油站有汽油 gas[i]
升。
你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i]
升。你从其中的一个加油站出发,开始时油箱为空。
如果你可以绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1。
说明:
- 如果题目有解,该答案即为唯一答案。
- 输入数组均为非空数组,且长度相同。
- 输入数组中的元素均为非负数。
示例 1:
输入: gas = [1,2,3,4,5] cost = [3,4,5,1,2] 输出: 3 解释: 从 3 号加油站(索引为 3 处)出发,可获得 4 升汽油。此时油箱有 = 0 + 4 = 4 升汽油 开往 4 号加油站,此时油箱有 4 - 1 + 5 = 8 升汽油 开往 0 号加油站,此时油箱有 8 - 2 + 1 = 7 升汽油 开往 1 号加油站,此时油箱有 7 - 3 + 2 = 6 升汽油 开往 2 号加油站,此时油箱有 6 - 4 + 3 = 5 升汽油 开往 3 号加油站,你需要消耗 5 升汽油,正好足够你返回到 3 号加油站。 因此,3 可为起始索引。
示例 2:
输入: gas = [2,3,4] cost = [3,4,3] 输出: -1 解释: 你不能从 0 号或 1 号加油站出发,因为没有足够的汽油可以让你行驶到下一个加油站。 我们从 2 号加油站出发,可以获得 4 升汽油。 此时油箱有 = 0 + 4 = 4 升汽油 开往 0 号加油站,此时油箱有 4 - 3 + 2 = 3 升汽油 开往 1 号加油站,此时油箱有 3 - 3 + 3 = 3 升汽油 你无法返回 2 号加油站,因为返程需要消耗 4 升汽油,但是你的油箱只有 3 升汽油。 因此,无论怎样,你都不可能绕环路行驶一周。
解法
Java
class Solution {
public int canCompleteCircuit(int[] gas, int[] cost) {
if(gas.length!=cost.length) return -1;
int sum=0,total=0,index=0;
for(int i=0;i<gas.length;i++){
int sy = gas[i] - cost[i];
sum+= sy;
total+= sy;
if(sum<0){
index=i+1;
sum=0;
}
}
if(total<0) return -1;
return index;
}
}
135. 分发糖果
题目描述
老师想给孩子们分发糖果,有 N 个孩子站成了一条直线,老师会根据每个孩子的表现,预先给他们评分。
你需要按照以下要求,帮助老师给这些孩子分发糖果:
- 每个孩子至少分配到 1 个糖果。
- 相邻的孩子中,评分高的孩子必须获得更多的糖果。
那么这样下来,老师至少需要准备多少颗糖果呢?
示例 1:
输入: [1,0,2] 输出: 5 解释: 你可以分别给这三个孩子分发 2、1、2 颗糖果。
示例 2:
输入: [1,2,2] 输出: 4 解释: 你可以分别给这三个孩子分发 1、2、1 颗糖果。 第三个孩子只得到 1 颗糖果,这已满足上述两个条件。
解法
Java
class Solution {
public int candy(int[] ratings) {
if (ratings == null || ratings.length == 0) return 0;
else if (ratings.length == 1) return 1;
int base = 1 ,cur = base ,sum = cur ,smallNum = 0 ,lastBigCur = cur;
for (int i = 1; i < ratings.length; i++) {
if (ratings[i - 1] < ratings[i]) {
smallNum = 0;
cur += base;
lastBigCur = cur;
sum += cur;
} else if (ratings[i - 1] == ratings[i]) {
smallNum = 0;
cur = base;
lastBigCur = cur;
sum += base;
} else {
if (cur == base) {
smallNum++;
sum = sum + cur + smallNum;
if (lastBigCur - 1 == smallNum) {
lastBigCur += base;
sum += base;
}
} else {
cur = base;
sum += cur;
}
}
}
return sum;
}
}
136. 只出现一次的数字
题目描述
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
示例 1:
输入: [2,2,1] 输出: 1
示例 2:
输入: [4,1,2,1,2] 输出: 4
解法
异或运算求解。
首先明确,两个相同的数异或之后的结果为 0。对该数组所有元素进行异或运算,结果就是那个只出现一次的数字。
Java
class Solution {
public int singleNumber(int[] nums) {
int res = 0;
for (int num : nums) {
res ^= num;
}
return res;
}
}
137. 只出现一次的数字 II
题目描述
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现了三次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
示例 1:
输入: [2,2,3,2] 输出: 3
示例 2:
输入: [0,1,0,1,0,1,99] 输出: 99
解法
统计所有数字每个位中 1 出现的次数,对于某个位,1 出现的次数一定是 3 的倍数 +1 或 0。对这个数 %3 得到的结果就是那个出现一次的数字在该位上的值。
Java
class Solution {
public int singleNumber(int[] nums) {
int[] bits = new int[32];
for (int num : nums) {
for (int i = 0; i < 32; ++i) {
bits[i] += (num & 1);
num >>= 1;
}
}
int res = 0;
for (int i = 0; i < 32; ++i) {
if (bits[i] % 3 == 1) {
res += (1 << i);
}
}
return res;
}
}
138. 复制带随机指针的链表
题目描述
给定一个链表,每个节点包含一个额外增加的随机指针,该指针可以指向链表中的任何节点或空节点。
要求返回这个链表的 深拷贝。
我们用一个由 n
个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index]
表示:
val
:一个表示Node.val
的整数。random_index
:随机指针指向的节点索引(范围从0
到n-1
);如果不指向任何节点,则为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 。
解法
Java
/**
* Definition for singly-linked list with a random pointer.
* class RandomListNode {
* int label;
* RandomListNode next, random;
* RandomListNode(int x) { this.label = x; }
* };
*/
public class Solution {
public RandomListNode copyRandomList(RandomListNode head) {
if (head == null) {
return null;
}
// step1
RandomListNode cur = head;
while (cur != null) {
RandomListNode node = new RandomListNode(cur.label);
node.next = cur.next;
cur.next = node;
cur = node.next;
}
// step2
cur = head;
while (cur != null) {
RandomListNode clone = cur.next;
if (cur.random != null) {
clone.random = cur.random.next;
}
cur = clone.next;
}
// step3
cur = head;
RandomListNode cloneHead = head.next;
while (cur.next != null) {
RandomListNode clone = cur.next;
cur.next = clone.next;
cur = clone;
}
return cloneHead;
}
}
139. 单词拆分
题目描述
给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。
说明:
- 拆分时可以重复使用字典中的单词。
- 你可以假设字典中没有重复的单词。
示例 1:
输入: s = "leetcode", wordDict = ["leet", "code"] 输出: true 解释: 返回 true 因为 "leetcode" 可以被拆分成 "leet code"。
示例 2:
输入: s = "applepenapple", wordDict = ["apple", "pen"] 输出: true 解释: 返回 true 因为"
applepenapple"
可以被拆分成"
apple pen apple"
。 注意你可以重复使用字典中的单词。
示例 3:
输入: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"] 输出: false
解法
Java
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
if (s == null || s.length() == 0) {
return false;
}
Set<String> words = new HashSet<>(wordDict);
boolean[] dp = new boolean[s.length() + 1];
dp[0] = true;
for (int i = 1; i <= s.length(); i++) {
for (int j = i - 1; j > -1; j--) {
dp[i] = dp[j] && words.contains(s.substring(j, i));
if (dp[i]) {
break;
}
}
}
return dp[s.length()];
}
}
140. 单词拆分 II
题目描述
给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,在字符串中增加空格来构建一个句子,使得句子中所有的单词都在词典中。返回所有这些可能的句子。
说明:
- 分隔时可以重复使用字典中的单词。
- 你可以假设字典中没有重复的单词。
示例 1:
输入: s = "catsanddog
" wordDict =["cat", "cats", "and", "sand", "dog"]
输出:[ "cats and dog", "cat sand dog" ]
示例 2:
输入: s = "pineapplepenapple" wordDict = ["apple", "pen", "applepen", "pine", "pineapple"] 输出: [ "pine apple pen apple", "pineapple pen apple", "pine applepen apple" ] 解释: 注意你可以重复使用字典中的单词。
示例 3:
输入: s = "catsandog" wordDict = ["cats", "dog", "sand", "and", "cat"] 输出: []
解法
Java
class Solution {
public List<String> wordBreak(String s, List<String> wordDict) {
return wordBreak(s,wordDict,new HashMap<>(16));
}
private List<String> wordBreak(String s, List<String> wordDict, HashMap<String, List<String>> map) {
List<String> list=new ArrayList<>();
if(map.containsKey(s)) return map.get(s);
if("".equals(s)){
list.add("");
return list;
}
for(String word:wordDict){
if(s.startsWith(word)){
List<String> res=wordBreak(s.substring(word.length()),wordDict,map);
for(String str:res){
list.add(word+("".equals(str) ?"":" ")+str);
}
}
}
map.put(s,list);
return list;
}
}
141. 环形链表
题目描述
给定一个链表,判断链表中是否有环。
为了表示给定链表中的环,我们使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos
是 -1
,则在该链表中没有环。
示例 1:
输入:head = [3,2,0,-4], pos = 1 输出:true 解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0 输出:true 解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1 输出:false 解释:链表中没有环。
进阶:
你能用 O(1)(即,常量)内存解决此问题吗?
解法
定义快慢指针 slow
、fast
,初始指向 head
。
快指针每次走两步,慢指针每次走一步,不断循环。当相遇时,说明链表存在环。如果循环结束依然没有相遇,说明链表不存在环。
Java
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public boolean hasCycle(ListNode head) {
ListNode slow = head;
ListNode fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) {
return true;
}
}
return false;
}
}
142. 环形链表 II
题目描述
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null
。
为了表示给定链表中的环,我们使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos
是 -1
,则在该链表中没有环。
说明:不允许修改给定的链表。
示例 1:
输入:head = [3,2,0,-4], pos = 1 输出:tail connects to node index 1 解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0 输出:tail connects to node index 0 解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1 输出:no cycle 解释:链表中没有环。
进阶:
你是否可以不用额外空间解决此题?
解法
先利用快慢指针判断链表是否有环,没有环则直接返回 null
。
若链表有环,我们分析快慢相遇时走过的距离。
对于慢指针,走过的距离为 S=X+Y
①;快指针走过的距离为 2S=X+Y+N(Y+Z)
②。如下图所示,其中 N
表示快指针与慢指针相遇时在环中所走过的圈数,而我们要求的环入口,也即是 X
的距离:
我们根据式子 ①②,得出 X+Y=N(Y+Z)
=> X=(N-1)(Y+Z)+Z
。
当 N=1
(快指针在环中走了一圈与慢指针相遇) 时,X=(1-1)(Y+Z)+Z
,即 X=Z
。此时只要定义一个 p
指针指向头节点,然后慢指针与 p
开始同时走,当慢指针与 p
相遇时,也就到达了环入口,直接返回 p
即可。
当 N>1
时,也是同样的,说明慢指针除了走 Z
步,还需要绕 N-1
圈才能与 p
相遇。
Java
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode slow = head, fast = head;
boolean hasCycle = false;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) {
hasCycle = true;
break;
}
}
if (!hasCycle) {
return null;
}
ListNode p = head;
while (p != slow) {
p = p.next;
slow = slow.next;
}
return p;
}
}
143. 重排链表
题目描述
给定一个单链表 L:L0→L1→…→Ln-1→Ln ,
将其重新排列后变为: L0→Ln→L1→Ln-1→L2→Ln-2→…
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
示例 1:
给定链表 1->2->3->4, 重新排列为 1->4->2->3.
示例 2:
给定链表 1->2->3->4->5, 重新排列为 1->5->2->4->3.
解法
先通过快慢指针找到链表中点,将链表划分为左右两部分。之后反转右半部分的链表,然后将左右两个链接依次连接即可。
Java
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public void reorderList(ListNode head) {
if (head == null || head.next == null) {
return;
}
ListNode slow = head;
ListNode fast = head.next;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
ListNode cur = slow.next;
slow.next = null;
ListNode pre = null;
while (cur != null) {
ListNode t = cur.next;
cur.next = pre;
pre = cur;
cur = t;
}
cur = head;
while (pre != null) {
ListNode t1 = cur.next;
cur.next = pre;
cur = t1;
ListNode t2 = pre.next;
pre.next = cur;
pre = t2;
}
}
}
144. 二叉树的前序遍历
题目描述
给定一个二叉树,返回它的 前序 遍历。
示例:
输入: [1,null,2,3] 1 \ 2 / 3 输出: [1,2,3]
进阶: 递归算法很简单,你可以通过迭代算法完成吗?
解法
Java
// 递归版本
/*
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
preorderTraversal(root, list);
return list;
}
private void preorderTraversal(TreeNode root, List<Integer> list) {
if (root == null) {
return;
}
list.add(root.val);
preorderTraversal(root.left, list);
preorderTraversal(root.right, list);
}
}
*/
// 非递归版本
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
while (root != null) {
list.add(root.val);
if (root.right != null) {
stack.push(root.right);
}
if (root.left != null) {
stack.push(root.left);
}
if (!stack.isEmpty()) {
root = stack.pop();
} else {
break;
}
}
return list;
}
}
145. 二叉树的后序遍历
题目描述
给定一个二叉树,返回它的 后序 遍历。
示例:
输入: [1,null,2,3] 1 \ 2 / 3 输出: [3,2,1]
进阶: 递归算法很简单,你可以通过迭代算法完成吗?
解法
Java
// 递归版本
/*
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
preorderTraversal(root, list);
return list;
}
private void preorderTraversal(TreeNode root, List<Integer> list) {
if (root == null) {
return;
}
list.add(root.val);
preorderTraversal(root.left, list);
preorderTraversal(root.right, list);
}
}
*/
// 非递归版本
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
while (root != null) {
list.add(root.val);
if (root.right != null) {
stack.push(root.right);
}
if (root.left != null) {
stack.push(root.left);
}
if (!stack.isEmpty()) {
root = stack.pop();
} else {
break;
}
}
return list;
}
}
146. LRU 缓存机制
题目描述
运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get
和 写入数据 put
。
获取数据 get(key)
- 如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。
写入数据 put(key, value)
- 如果密钥不存在,则写入其数据值。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。
进阶:
你是否可以在 O(1) 时间复杂度内完成这两种操作?
示例:
LRUCache cache = new LRUCache( 2 /* 缓存容量 */ ); cache.put(1, 1); cache.put(2, 2); cache.get(1); // 返回 1 cache.put(3, 3); // 该操作会使得密钥 2 作废 cache.get(2); // 返回 -1 (未找到) cache.put(4, 4); // 该操作会使得密钥 1 作废 cache.get(1); // 返回 -1 (未找到) cache.get(3); // 返回 3 cache.get(4); // 返回 4
解法
Java
//双向链表的节点
class Node{
public int key;
public int val;
public Node pre;//指向前面的指针
public Node next;//指向后面的指针
public Node(int key,int value){
this.val = value;
this.key = key;
}
}
class LRUCache {
int capacity;//容量
Node head;//双向链表的头,维护这个指针,因为set,get时需要在头部操作
Node end;//双向链表的尾,set时,要是满了,需要将链表的最后一个节点remove
HashMap<Integer,Node> map = new HashMap<Integer,Node>();//hash表
public LRUCache(int capacity) {
this.capacity = capacity;
}
//添加,删除尾部,插入头部的操作
public void remove(Node node){
Node cur = node;
Node pre = node.pre;
Node post = node.next;
if(pre == null){//说明cur是头部节点
head = post;
}
else pre.next = post;//更新指针,删除
if(post == null){//说明cur是最后的节点
end = pre;
}
else post.pre = pre;
}
public void setHead(Node node){
//直接插入
node.next = head;
node.pre = null;
if(head != null) head.pre = node;//防止第一次插入时为空
head = node;
if(end==null) end = node;
}
public int get(int key) {
if(map.containsKey(key)){
//需要把对应的节点调整到头部
Node latest = map.get(key);
remove(latest);
setHead(latest);
//返回value
return latest.val;
}
else return -1;
}
public void put(int key, int value) {
if(map.containsKey(key)){//这个key原来存在
//只需要把key对应的node提到最前面,更新value
Node oldNode = map.get(key);
oldNode.val = value;
remove(oldNode);
setHead(oldNode);
}
else{
//这个key原来不存在,需要重新new出来
Node newNode = new Node(key,value);
//接下来要考虑容量
if(map.size() < capacity){
setHead(newNode);
map.put(key, newNode);
}
else{
//容量不够,需要先将map中,最不常使用的那个删除了删除
map.remove(end.key);
//接下来更新双向链表
remove(end);
setHead(newNode);
//放入新的
map.put(key, newNode);
}
}
}
}
/**
* Your LRUCache object will be instantiated and called as such:
* LRUCache obj = new LRUCache(capacity);
* int param_1 = obj.get(key);
* obj.put(key,value);
*/
147. 对链表进行插入排序
题目描述
对链表进行插入排序。
插入排序的动画演示如上。从第一个元素开始,该链表可以被认为已经部分排序(用黑色表示)。
每次迭代时,从输入数据中移除一个元素(用红色表示),并原地将其插入到已排好序的链表中。
插入排序算法:
- 插入排序是迭代的,每次只移动一个元素,直到所有元素可以形成一个有序的输出列表。
- 每次迭代中,插入排序只从输入数据中移除一个待排序的元素,找到它在序列中适当的位置,并将其插入。
- 重复直到所有输入数据插入完为止。
示例 1:
输入: 4->2->1->3 输出: 1->2->3->4
示例 2:
输入: -1->5->3->4->0 输出: -1->0->3->4->5
解法
遍历链表,每次将遍历到的结点 cur 与前一个结点 pre 进行值比较:
- 若结点 cur 的值比 pre 的大,说明当前 cur 已在正确的位置,直接往下遍历。
- 否则,从链表第一个结点开始遍历,将结点 cur 插入到正确的位置。
依次遍历,直至 cur 指向空,遍历结束。
Java
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode insertionSortList(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode dummy = new ListNode(head.val);
dummy.next = head;
ListNode pre = dummy, cur = head;
while (cur != null) {
if (pre.val <= cur.val) {
pre = cur;
cur = cur.next;
continue;
}
ListNode p = dummy;
while (p.next.val <= cur.val) {
p = p.next;
}
ListNode t = cur.next;
cur.next = p.next;
p.next = cur;
pre.next = t;
cur = t;
}
return dummy.next;
}
}
148. 排序链表
题目描述
在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序。
示例 1:
输入: 4->2->1->3 输出: 1->2->3->4
示例 2:
输入: -1->5->3->4->0 输出: -1->0->3->4->5
解法
先用快慢指针找到链表中点,然后分成左右两个链表,递归排序左右链表。最后合并两个排序的链表即可。
Java
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode sortList(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode slow = head, fast = head.next;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
ListNode t = slow.next;
slow.next = null;
ListNode l1 = sortList(head);
ListNode l2 = sortList(t);
ListNode dummy = new ListNode(0);
ListNode cur = dummy;
while (l1 != null && l2 != null) {
if (l1.val <= l2.val) {
cur.next = l1;
l1 = l1.next;
} else {
cur.next = l2;
l2 = l2.next;
}
cur = cur.next;
}
cur.next = l1 == null ? l2 : l1;
return dummy.next;
}
}
149. 直线上最多的点数
题目描述
给定一个二维平面,平面上有 n 个点,求最多有多少个点在同一条直线上。
示例 1:
输入: [[1,1],[2,2],[3,3]] 输出: 3 解释: ^ | | o | o | o +-------------> 0 1 2 3 4
示例 2:
输入: [[1,1],[3,2],[5,3],[4,1],[2,3],[1,4]] 输出: 4 解释: ^ | | o | o o | o | o o +-------------------> 0 1 2 3 4 5 6
解法
Java
class Solution {
public int maxPoints(Point[] points) {
if( points.length <= 2 ) return points.length;
int max = 2 ;
for( int i = 0 ; i < points.length ; i++ ){
int samePosition = 0;
int sameSlope = 1;
for( int j = i + 1 ; j < points.length ; j++ ){
long x1 = points[j].x - points[i].x;
long y1 = points[j].y - points[i].y;
if( x1 == 0 && y1 == 0 ){
samePosition++;
} else {
sameSlope++;
for(int k = j + 1 ; k < points.length ; k++ ){
long x2 = points[k].x - points[i].x;
long y2 = points[k].y - points[i].y;
if ( x1 * y2 == x2 * y1 ) sameSlope++;
}
}
if(max < (samePosition + sameSlope)) max = samePosition + sameSlope;
sameSlope = 1;
}
}
return max;
}
}
150. 逆波兰表达式求值
题目描述
根据逆波兰表示法,求表达式的值。
有效的运算符包括 +
, -
, *
, /
。每个运算对象可以是整数,也可以是另一个逆波兰表达式。
说明:
- 整数除法只保留整数部分。
- 给定逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。
示例 1:
输入: ["2", "1", "+", "3", "*"] 输出: 9 解释: ((2 + 1) * 3) = 9
示例 2:
输入: ["4", "13", "5", "/", "+"] 输出: 6 解释: (4 + (13 / 5)) = 6
示例 3:
输入: ["10", "6", "9", "3", "+", "-11", "*", "/", "*", "17", "+", "5", "+"] 输出: 22 解释: ((10 * (6 / ((9 + 3) * -11))) + 17) + 5 = ((10 * (6 / (12 * -11))) + 17) + 5 = ((10 * (6 / -132)) + 17) + 5 = ((10 * 0) + 17) + 5 = (0 + 17) + 5 = 17 + 5 = 22
解法
栈实现。
遍历数组,遇到数字则压入栈中,遇到运算符号,则从栈中弹出右、左操作数,运算过后,将结果压入栈中。
遍历结束后,返回栈中的唯一元素。
Java
class Solution {
public int evalRPN(String[] tokens) {
Deque<Integer> s = new ArrayDeque<>();
int left, right;
for (String token : tokens) {
switch(token) {
case "+":
right = s.pop();
left = s.pop();
s.push(left + right);
break;
case "-":
right = s.pop();
left = s.pop();
s.push(left - right);
break;