目录(序号为leetcode题号)
326. 3 的幂
给定一个整数,写一个函数来判断它是否是 3 的幂次方。如果是,返回 true ;否则,返回 false 。
整数 n 是 3 的幂次方需满足:存在整数 x 使得 n == 3x
示例 :
输入:n = 27
输出:true
进阶:你能不使用循环或者递归来完成本题吗?
提示:
- -231 <= n <= 231 - 1
解题思路:打表,将所有整数范围内的3的幂,存放进哈希表中,直接判断。
class Solution {
static HashSet<Integer> set;
static {
set = new HashSet<>();
int n = 1;
while(n > 0){
set.add(n);
n *= 3;
}
}
public boolean isPowerOfThree(int n) {
return set.contains(n);
}
}
328. 奇偶链表
给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。
请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。
示例 :
输入: 2->1->3->5->6->4->7->NULL
输出: 2->3->6->7->1->5->4->NULL
说明:
- 应当保持奇数节点和偶数节点的相对顺序。
- 链表的第一个节点视为奇数节点,第二个节点视为偶数节点,以此类推。
解题思路:直观的解法。
public ListNode oddEvenList(ListNode head) {
int k = 1;
ListNode oneHead,twoHead;
ListNode p1,p2;
oneHead = new ListNode();
twoHead = new ListNode();
p1 = oneHead;
p2 = twoHead;
while(head != null){
if(k % 2 ==1){
// 奇数节点
p1.next = head;
p1 = p1.next;
}else{
// 偶数节点
p2.next = head;
p2 = p2.next;
}
k++;
head = head.next;
}
p1.next = null;
p2.next = null;
p1.next = twoHead.next;
return oneHead.next;
}
329. 矩阵中的最长递增路径
给定一个 m x n 整数矩阵 matrix ,找出其中最长递增路径的长度。
对于每个单元格,你可以往上,下,左,右四个方向移动。 你不能在对角线 方向上移动或移动到边界外(即不允许环绕)。
示例:
输入:matrix = [[9,9,4],[6,6,8],[2,1,1]]
输出:4
解释:最长递增路径为 [1, 2, 6, 9]。
解题思路:DFS+记忆化搜索,对于每一个点进行深度优先遍历,寻找最长的递增路径长度,同时将已经找到的对于每一个点的最长递增路径长度保存下来。避免重复计算。
public int longestIncreasingPath(int[][] matrix) {
int m = matrix.length;
int n = matrix[0].length;
// 记录最大长度
int max = Integer.MIN_VALUE;
// 记录以每一个点为起始位置的长度
int[][] maxLen = new int[m][n];
// 遍历每一个点,找到以当前点为起始位置的最大递增路径的长度
for(int i = 0;i < m;i++){
for(int j = 0;j < n;j++){
// 如果该点没有计算过,进行dfs计算
if(maxLen[i][j] == 0){
dfs(matrix,maxLen,i,j);
}
// 比较历史最大长度与当前点的长度,选择最大的长度
max = Math.max(maxLen[i][j],max);
}
}
return max;
}
// 方法功能,找到matrix[i][j]的最大长度。
// 参数说明,matrix矩阵,maxLen记录每一个点的单元格为起始的最大长度,i,j当前位置
int[][] dir = {{0, 1},{1, 0},{0, -1},{-1, 0}};
public int dfs(int[][] matrix,int[][] maxLen,int i,int j){
// 初始化为 1
maxLen[i][j] = 1;
for(int di = 0;di < 4;di++) {
// 向四周扩散寻找增长路径,保证在矩阵内,并且大于当前点
int newi = dir[di][0] + i;
int newj = dir[di][1] + j;
if (0 <= newi && newi < matrix.length &&
0 <= newj && newj < matrix[0].length &&
matrix[newi][newj] > matrix[i][j]) {
// 该点已经计算过
if (maxLen[newi][newj] != 0) {
// 可以扩展的点长度+1,加上当前的点,与当前点大小比较,选择最大的值
maxLen[i][j] = Math.max(maxLen[newi][newj] + 1,maxLen[i][j]);
} else {
// 要扩展的点,没有计算过,进行dfs,计算结果+1,与当前点的值比较,选择最大的值
maxLen[i][j] = Math.max(dfs(matrix, maxLen, newi,newj) + 1,maxLen[i][j]);
}
}
}
// 返回当前点的最大长度
return maxLen[i][j];
}
334. 递增的三元子序列
给你一个整数数组 nums ,判断这个数组中是否存在长度为 3 的递增子序列。
如果存在这样的三元组下标 (i, j, k) 且满足 i < j < k ,使得 nums[i] < nums[j] < nums[k] ,返回 true ;否则,返回 false 。
输入:nums = [2,1,5,0,4,6]
输出:true
解释:三元组 (3, 4, 5) 满足题意,因为 nums[3] == 0 < nums[4] == 4 < nums[5] == 6
方法一:动态规划(超时了)
解题思路:动态规划,dp[i] 表示,当前递增子序列的长度。
状态转移方程:dp[i] = max(dp [ k ] + 1,dp[i]),如果 nums[i] > nums[k],(0 <= k < i)
public boolean increasingTriplet(int[] nums) {
int len = nums.length;
int[] dp = new int[len];
for(int i = 0;i < len;i++){
dp[i] = 1;
for(int j = 0;j < i;j++){
if(nums[i] > nums[j]){
dp[i] = Math.max(dp[j] + 1,dp[i]);
if(dp[i] == 3){
return true;
}
}
}
}
return false;
}
方法二:维护两个值,出现的最小值和出现的最大值,如果数组中又出现了一个比最大值还大的值,正好构成递增的三元子序列。
public boolean increasingTriplet(int[] nums) {
int len = nums.length;
int min = Integer.MAX_VALUE,max = Integer.MAX_VALUE;
for(int i = 0;i < len;i++){
if(nums[i] <= min){
// 维护最小值
min = nums[i];
}else if(nums[i] < max){
// 维护最大值
max = nums[i];
}else if(nums[i] > max){
// 出现比最大值更大的数,构成三元子序列
return true;
}
}
return false;
}
341. 扁平化嵌套列表迭代器
给你一个嵌套的整数列表 nestedList 。每个元素要么是一个整数,要么是一个列表;该列表的元素也可能是整数或者是其他列表。请你实现一个迭代器将其扁平化,使之能够遍历这个列表中的所有整数。
实现扁平迭代器类 NestedIterator :
- NestedIterator(List nestedList) 用嵌套列表 nestedList 初始化迭代器。
- int next() 返回嵌套列表的下一个整数。
- boolean hasNext() 如果仍然存在待迭代的整数,返回 true ;否则,返回 false 。
你的代码将会用下述伪代码检测:
initialize iterator with nestedList
res = []
while iterator.hasNext()
append iterator.next() to the end of res
return res
示例:
输入:nestedList = [[1,1],2,[1,1]]
输出:[1,1,2,1,1]
解释:通过重复调用 next 直到 hasNext 返回 false,next 返回的元素的顺序应该是:[1,1,2,1,1]。
提示:
- 1 <= nestedList.length <= 500
- 嵌套列表中的整数值在范围 [-106, 106] 内
解题思路:DFS,将列表中的所有值放入 list集合中。再使用原生的迭代器完成功能即可。
public class NestedIterator implements Iterator<Integer> {
List<Integer> list = new LinkedList<>();
Iterator<Integer> iterator;
public NestedIterator(List<NestedInteger> nestedList) {
// dfs将列表中所有的值放入 list集合
dfs(nestedList);
// 使用list集合得到迭代器344. 反转字符串
iterator = list.iterator();
}
private void dfs(List<NestedInteger> nestedList){
int len = nestedList.size();
for(int i = 0;i < len;i++){
if(nestedList.get(i).isInteger()){
// 是单个整数
list.add(nestedList.get(i).getInteger());
}else{
// 是列表
dfs(nestedList.get(i).getList());
}
}
}
@Override
public Integer next() {
return iterator.next();
}
@Override
public boolean hasNext() {
return iterator.hasNext();
}
}
344. 反转字符串
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。
不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。
示例:
输入:s = ["h","e","l","l","o"]
输出:["o","l","l","e","h"]
提示:
- 1 <= s.length <= 105
- s[i] 都是 ASCII 码表中的可打印字符
解题思路:前后元素互换即可
public void reverseString(char[] s) {
int i = 0;
int j = s.length - 1;
char c;
while(i < j){
c = s[i];
s[i] = s[j];
s[j] = c;
i++;
j--;
}
}
347. 前 K 个高频元素
给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。
示例 :
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
提示:
- 1 <= nums.length <= 105
- k 的取值范围是 [1, 数组中不相同的元素的个数]
- 题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的
进阶:你所设计算法的时间复杂度 必须 优于 O(n log n) ,其中 n 是数组大小。
解题思路:使用 hashMap 统计各个元素出现的次数,然后对出现次数进行排序,排序后前k个元素就是要找的结果。
public int[] topKFrequent(int[] nums, int k) {
HashMap<Integer,Integer> map = new HashMap<>();
// 统计出现次数
for(int i = 0; i<nums.length;i++){
map.put(nums[i],map.getOrDefault(nums[i],0) + 1);
}
int[][] list = new int[map.size()][2];
int index = 0;
// 将map中的键值赋值给数组
for(Map.Entry entry:map.entrySet()){
list[index][0] = (int)entry.getKey();
list[index][1] = (int)entry.getValue();
index++;
}
// 按次数对数组进行排序
Arrays.sort(list, (a, b) -> b[1]-a[1]);
int[] ans = new int[k];
// 将前k个赋值给ans
for(int i = 0;i<k;i++){
ans[i] = list[i][0];
}
// 返回最终结果
return ans;
}
350. 两个数组的交集 II
给你两个整数数组 nums1 和 nums2 ,请你以数组形式返回两数组的交集。返回结果中每个元素出现的次数,应与元素在两个数组中都出现的次数一致(如果出现次数不一致,则考虑取较小值)。可以不考虑输出结果的顺序。
示例 :
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[4,9]
提示:
- 1 <= nums1.length, nums2.length <= 1000
- 0 <= nums1[i], nums2[i] <= 1000
方法一:排序+双指针
解题思路:对两个数组进行排序,然后从头开始遍历,比较,将相等的加入结果集。
public int[] intersect(int[] nums1, int[] nums2) {
// 对数组进行排序
Arrays.sort(nums1);
Arrays.sort(nums2);
List<Integer> list = new LinkedList<>();
int i = 0;
int j = 0;
// 双指针
while(i < nums1.length && j < nums2.length){
if(nums1[i] < nums2[j]){
i++;
}else if(nums2[j] < nums1[i]){
j++;
}else{
list.add(nums1[i]);
i++;
j++;
}
}
// 把集合中的元素赋值给数组
int[] ans = new int[list.size()];
i = 0;
for(int num : list){
ans[i++] = num;
}
return ans;
}
方法二:哈希表
解题思路:把较短的数组中的所有元素和出现的次数,存放入哈希表中。然后使用较长的数组的所有元素与表中的元素和初现次数做比较。
public int[] intersect(int[] nums1, int[] nums2) {
HashMap<Integer,Integer> hashMap = new HashMap<>();
// 使得nums1数组的长度最小
if(nums1.length > nums2.length){
int[] tmp = nums1;
nums1 = nums2;
nums2 = tmp;
}
// nums1数组元素放入哈希表
for(int i = 0;i<nums1.length;i++){
hashMap.put(nums1[i],hashMap.getOrDefault(nums1[i],0) + 1);
}
List<Integer> list = new LinkedList<>();
// nums2元素在哈希表寻找
for(int i = 0;i<nums2.length;i++){
// 在哈希表中存在,说明该元素在交集中。
if(hashMap.containsKey(nums2[i])){
// 将哈希表中次数减1
int k = hashMap.get(nums2[i]);
k--;
if(k == 0){
// 次数为0,移除
hashMap.remove(nums2[i]);
}else{
hashMap.put(nums2[i],k);
}
// 将元素加入集合中
list.add(nums2[i]);
}
}
// 将集合中的结果放入
int[] ans = new int[list.size()];
int i = 0;
for(int num:list){
ans[i++] = num;
}
return ans;
}
371. 两整数之和
给你两个整数 a 和 b ,不使用 运算符 + 和 - ,计算并返回两整数之和。
示例:
输入:a = 5, b = 6
输出:11
解题思路:使用位运算。
在二进制中:0 + 0 = 0,0 + 1 = 1,1 + 0 = 1,1 + 1 = 1(产生进位)
使用异或运算恰好能完成二进制的加法。只是丢失了进位。
使用与运算,1&1 = 1,使得结果左移一位,恰好就是进位值。
示例:
a = 5,b=6。二进制为 a = 0101,b = 0110
a+b = 11,二进制为 1011。
如:a ^ b = 0011,a & b << 1 = 1000(左移一位),a ^ b + a &b << 1 就是a+b的结果。
就把 a + b的运算转换为 a^b + a&b << 1 的结果。进行循环,每次对 a&b的结果左移一位,最终 a&b<<1必然为0。
a = 0011,b=1000
a ^ b = 1011
a & b << 1 = 0。
此时a就是正确结果,计算完成。
public int getSum(int a, int b) {
while (b != 0) {
int carry = (a & b) << 1;
a = a ^ b;
b = carry;
}
return a;
}
378. 有序矩阵中第 K 小的元素
给你一个 n x n 矩阵 matrix ,其中每行和每列元素均按升序排序,找到矩阵中第 k 小的元素。
请注意,它是 排序后 的第 k 小元素,而不是第 k 个 不同 的元素。
示例:
输入:matrix = [[1,5,9],[10,11,13],[12,13,15]], k = 8
输出:13
解释:矩阵中的元素为 [1,5,9,10,11,12,13,13,15],第 8 小元素是 13
提示:
- n == matrix.length
- n == matrix[i].length
- 1 <= n <= 300
- -109 <= matrix[i][j] <= 109
- 题目数据 保证 matrix 中的所有行和列都按 非递减顺序 排列
- 1 <= k <= n2
方法一:归并排序
解题思路:把矩阵看成对 n个长度为n的有序数组的归并排序,排序到第k个小的元素为止。
public int kthSmallest(int[][] matrix, int k) {
int n = matrix.length;
// 使用最小堆,堆的根节点是最小值
PriorityQueue<int[]> priorityQueue = new PriorityQueue<>((a,b)->(a[0]-b[0]));
// 将所以数组的第一个元素加入堆中
for(int i = 0;i<n;i++){
// 数组的第一个值为 矩阵中节点的值,第二个,三个表示所在矩阵的位置
priorityQueue.offer(new int[]{matrix[i][0],i,0});
}
for(int i = 0;i < k-1;i++){
// 抛出最小的元素
int[] min = priorityQueue.poll();
if(min[2] + 1 < n){
// 把最小的元素的后一个元素加入堆
priorityQueue.offer(new int[]{matrix[min[1]][min[2] + 1],min[1],min[2] + 1});
}
}
return priorityQueue.peek()[0];
}
方法二:二分查找
解题思路:在matrix中,matrix[0][0] 是最小值,matrix[n-1][n-1]是最大值。第k小元素必定在[matrix[0][0], matrix[n-1][n-1]]区间内。
在 [matrix[0][0], matrix[n-1][n-1]] 区间中去猜第k小元素是哪一个。猜!!!使用二分查找加快猜对的速度。
初始化 left = matrix[0][0],right = matrix[n-1][n-1]。每次取 left和right的中间值去猜。
mid = left+(right-left)/ 2;
在矩阵中统计出 小于等于 mid 的个数nums,nums与 k 进行比较,如果 nums > k,那么说明猜的数mid过大,第k个最小值应该在 left到mid-1的区间中。如果 nums < k,说明猜的数mid过小,第k个最小的值应该在mid + 1到right的区间中。继续猜,直到 left = right,说明left就是第 k 个最小的数。
public int kthSmallest(int[][] matrix, int k) {
int n = matrix.length;
int left = matrix[0][0];
int right = matrix[n - 1][n - 1];
while (left < right) {
// 从left,right 区间中选择中间值去猜
int mid = left + ((right - left) >> 1);
if (check(matrix, mid, k, n)) {
// 小于等于mid的数量大于等于k,猜的数太大
right = mid;
} else {
// 小于等于mid的数量小于k,猜的数太小
left = mid + 1;
}
}
return left;
}
// 统计比小于等于mid的数量,并与k相比较
public boolean check(int[][] matrix, int mid, int k, int n) {
int i = n - 1;
int j = 0;
int num = 0;
// 从右下角开始统计
while (i >= 0 && j < n) {
if (matrix[i][j] <= mid) {
// 每次+i+1,为 matrix[0] 到 matrix[i]中第j列的数量
num += i + 1;
// 向右继续比较
j++;
} else {
// 向上继续比较
i--;
}
}
return num >= k;
}