和数据结构相关的题目
1.数组
448. Find All Numbers Disappeared in an Array (Easy)
问题描述
给你一个含 n 个整数的数组 nums ,其中 nums[i] 在区间 [1, n] 内。请你找出所有在 [1, n] 范围内但没有出现在 nums 中的数字,并以数组的形式返回结果。
输入输出样例
示例 1:
输入:nums = [4,3,2,7,8,2,3,1]
输出:[5,6]
示例 2:
输入:nums = [1,1]
输出:[2]
思路
思路1:使用一个哈希数组来统计每个元素出现的次数,然后返回次数为零的位置。
思路2:在原来数组的基础上,将出现过得位置上的元素标记为负值,这样再统计一边如果某位置的元素不为负值则该位置就是未出现的数。
代码(思路1)
public List<Integer> findDisappearedNumbers(int[] nums) {
List<Integer> list = new ArrayList<>();
int[] hash = new int[nums.length+1];
for(int num:nums){
hash[num]++;
}
for(int i=1;i<hash.length;i++){
if(hash[i]==0){
list.add(i);
}
}
return list;
}
代码(思路2)
public List<Integer> findDisappearedNumbers(int[] nums) {
List<Integer> list = new ArrayList<>();
for(int i=0;i<nums.length;i++){
int index = Math.abs(nums[i]) - 1;
if(nums[index]>0) nums[index] = -nums[index];
}
for(int i=0;i<nums.length;i++){
if(nums[i]>0) list.add(i+1);
}
return list;
}
48. Rotate Image (Medium)
题目描述
给定一个n × n 的矩阵,求它顺时针旋转90 度的结果,且必须在原矩阵上修改(in-place)。
怎样能够尽量不创建额外储存空间呢?
输入输出样例
输入和输出都是一个二维整数矩阵。
示例 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]]
思路
思路二:
对于矩阵中的第一行而言,在旋转后,它出现在倒数第一列的位置:
它们的关系为:newArray[j][n-1-i]=array[i][j]
思路二
对矩阵先进行上下翻转,在进行主对角线翻转,就相当于旋转了90度
首先,需要理解基础的对称操作,对于 nxn 的矩阵 matrix,各种对称的转移式如下:
上下对称:matrix[i][j] -> matrix[n-i-1][j],(列不变)
左右对称:matrix[i][j] -> matrix[i][n-j-1],(行不变)
主对角线对称:matrix[i][j] -> matrix[j][i],(行列互换)
副对角线对称:matrix[i][j] -> matrix[n-j-1][n-i-1] (行列均变,且互换)
那么,对于顺时针 90° 旋转,即本题,先写出转移式:
matrix[i][j] -> matrix[j][n-i-1],
可以观察到,我们希望原来的列j不变,且要交换行列位置。
因此可以分解为:上下对称 + 主对角线对称 或者 主对角线对称 + 左右对称,
代码(思路1)
class Solution {
public void rotate(int[][] matrix) {
int m = matrix.length;
int n = matrix[0].length;
int[][] array = new int[m][n];
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
array[j][n-1-i]=matrix[i][j];
}
}
for(int i=0;i<m;i++){
for (int j = 0; j < n; j++) {
matrix[i][j]=array[i][j];
}
}
}
}
代码(思路2)
class Solution {
public void rotate(int[][] matrix) {
int m = matrix.length;
int n = matrix[0].length;
int tem;
for(int i=0;i<m/2;i++){ //水平翻转
for(int j=0;j<n;j++){
tem = matrix[i][j];
matrix[i][j] = matrix[n-1-i][j];
matrix[n-1-i][j] = tem;
}
}
for (int i = 0; i < m; i++) {
for (int j = i; j < n; j++) {
tem = matrix[i][j];
matrix[i][j]=matrix[j][i];
matrix[j][i]=tem;
}
}
}
}
240. Search a 2D Matrix II (Medium)
问题描述
编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性:
每行的元素从左到右升序排列。
每列的元素从上到下升序排列。
示例 1:
输入:matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21,23,26,30]], target = 5
输出:true
示例 2:
输入:matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21,23,26,30]], target = 20
输出:false
思路
从右上角开始找,如果比target小下移,如果比target大左移。
代码
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
int m = matrix.length;
int n = matrix[0].length;
int i=0,j=n-1;
while (i<m && j>=0){
if(matrix[i][j]>target) j--;
else if(matrix[i][j]<target) i++;
else return true;
}
return false;
}
}
769. Max Chunks To Make Sorted (Medium)
问题描述
给定一个长度为 n 的整数数组 arr ,它表示在 [0, n - 1] 范围内的整数的排列。
我们将 arr 分割成若干 块 (即分区),并对每个块单独排序。将它们连接起来后,使得连接的结果和按升序排序后的原数组相同。
返回数组能分成的最多块数量。
输入输出样例
示例 1:
输入: arr = [4,3,2,1,0]
输出: 1
解释:
将数组分成2块或者更多块,都无法得到所需的结果。
例如,分成 [4, 3], [2, 1, 0] 的结果是 [3, 4, 0, 1, 2],这不是有序的数组。
示例 2:
输入: arr = [1,0,2,3,4]
输出: 4
解释:
我们可以把它分成两块,例如 [1, 0], [2, 3, 4]。
然而,分成 [1, 0], [2], [3], [4] 可以得到最多的块数。
思路
当遍历到第i个位置时,如果可以切分为块,那前i个位置的最大值一定等于i。当最大值为i时,说明了前i个元素一定是有序的。
否则,一定有比i小的数划分到后面的块,那块排序后,一定不满足升序。
代码
class Solution {
public int maxChunksToSorted(int[] arr) {
int count = 0;
int max = 0;
for(int i=0;i<arr.length;i++){
max = Math.max(max,arr[i]);
if(max == i){
count++;
}
}
return count;
}
}
栈
232. Implement Queue using Stacks (Easy)
问题描述
请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty):
实现 MyQueue 类:
void push(int x) 将元素 x 推到队列的末尾
int pop() 从队列的开头移除并返回元素
int peek() 返回队列开头的元素
boolean empty() 如果队列为空,返回 true ;否则,返回 false
说明:
你 只能 使用标准的栈操作 —— 也就是只有 push to top, peek/pop from top, size, 和 is empty 操作是合法的。
你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。
思路
用两个栈来实现一个队列,可以看做以下状态:
入队:只压入左侧的栈
出队:从右侧出栈;如果右侧为空,先把左侧的全部弹出压入右侧的栈,之后再出栈
队首:右侧不为空时的栈顶元素;如果右侧为空,先把左侧的全部弹出压入右侧的栈,之后再取右侧的栈顶元素。
判空:两个栈都为空时,队列为空
代码
class MyQueue {
private Deque<Integer> stack1;
private Deque<Integer> stack2;
public MyQueue() {
stack1 = new ArrayDeque<>();
stack2= new ArrayDeque<>();
}
public void push(int x) {
stack1.offerLast(x);
}
public int pop() {
if(empty())return -1;
if(!stack2.isEmpty()){
return stack2.pollLast();
}
while (!stack1.isEmpty()){
int tem = stack1.pollLast();
stack2.offerLast(tem);
}
return stack2.pollLast();
}
public int peek() {
if(stack2.isEmpty()) return stack1.peekFirst();
else return stack2.peekLast();
}
public boolean empty() {
return stack1.isEmpty() && stack2.isEmpty();
}
}
/**
* Your MyQueue object will be instantiated and called as such:
* MyQueue obj = new MyQueue();
* obj.push(x);
* int param_2 = obj.pop();
* int param_3 = obj.peek();
* boolean param_4 = obj.empty();
*/
155. Min Stack (Easy)
问题描述
设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。
实现 MinStack 类:
MinStack() 初始化堆栈对象。
void push(int val) 将元素val推入堆栈。
void pop() 删除堆栈顶部的元素。
int top() 获取堆栈顶部的元素。
int getMin() 获取堆栈中的最小元素。
思路
使用一个辅助栈同时记录当前栈最小的值。
进栈:当一个值进栈时,如果该值小于辅助栈栈顶元素则也同时进辅助栈
出栈:当一个值出栈时,其值等于辅助栈栈顶元素则辅助栈栈顶元素也出栈
最小值:返回辅助栈栈顶元素
代码
class MinStack {
Deque<Integer> stack; //主栈
Deque<Integer> minStack; //存放当前主栈中最小值的栈
public MinStack() {
minStack = new LinkedList<>();
stack = new LinkedList<>();
}
public void push(int val) {
if(minStack.isEmpty() || val<=minStack.peek()){
minStack.push(val);
}
stack.push(val);
}
public void pop() {
if(!minStack.isEmpty() && minStack.peek().equals(stack.peek())){
minStack.pop();
}
stack.pop();
}
public int top() {
return stack.peek();
}
public int getMin() {
return minStack.peek();
}
}
20. Valid Parentheses (Easy)
问题描述
给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
输入输出样例
示例 1:
输入:s = “()”
输出:true
示例 2:
输入:s = “()[]{}”
输出:true
思路
先将括号限制为()一种。新建一个栈,在遍历字符串数组时,如果遇到(
则入栈,如果遇到)
则出栈,如果栈为空没办法出栈时则说明有一个括号缺少左边,返回false。如果遍历完所有字符串,栈不为空,则说明有括号左半边缺少右半边;如果为空则返回true。
利用这种思路可以扩展到多种括号,只需出栈前和栈顶元素判断一下即可。
代码
class Solution {
public boolean isValid(String s) {
Deque<Character> stack = new LinkedList<>();
for(char a:s.toCharArray()){
if(a == '(' || a =='[' || a =='{'){
stack.push(a);
}
if(a == ')'){
if(stack.isEmpty() || stack.peek()!='(') return false;
stack.pop();
}
if(a == ']'){
if(stack.isEmpty() || stack.peek()!='[') return false;
stack.pop();
}
if(a == '}'){
if(stack.isEmpty() || stack.peek()!='{') return false;
stack.pop();
}
}
return stack.isEmpty();
}
}
739. Daily Temperatures (Medium)
问题描述
给定一个整数数组 temperatures ,表示每天的温度,返回一个数组 answer ,其中 answer[i] 是指对于第 i 天,下一个更高温度出现在几天后。如果气温在这之后都不会升高,请在该位置用 0 来代替。
输入输出样例
示例 1:
输入: temperatures = [73,74,75,71,69,72,76,73]
输出: [1,1,4,2,1,1,0,0]
示例 2:
输入: temperatures = [30,40,50,60]
输出: [1,1,1,0]
思路
思路1:暴力解法,每遍历一个数用另外一个指针找到下一个大于大的值,求两个位置的差值。
思路2:使用单调栈。维护一个单调递减的单调栈,一个元素索引i入栈的条件是当前i元素小于栈顶元素。如果大于栈顶元素,就要将栈顶元素弹出来,弹出来的pop位置的下一个大于pop的位置的元素就是i,于是可以先把pop位置的result进行填充:result[pop]=i-pop;小于栈顶元素的话直接入栈。使用单调栈本质上是找下一个大于该值(通过x找y)的反问题——y是一个x的结果,先把y对应的x解决。
代码(思路一)
class Solution {
public int[] dailyTemperatures(int[] temperatures) {
int n = temperatures.length;
for(int i=0;i<n;i++){
int j=i+1;
while (j<n && temperatures[j]<=temperatures[i]){//找到下一个大于大于i元素的位置
j++;
}
if(j==n)j = i; //如果没找到,该位置为0
temperatures[i]=j-i;
}
return temperatures;
}
}
代码(思路二)
class Solution {
public int[] dailyTemperatures(int[] temperatures) {
Deque<Integer> stack = new LinkedList<>(); //维护一个单调递减栈
int n = temperatures.length;
int[] result = new int[n];
for(int i=0;i<n;i++){
while (!stack.isEmpty() && temperatures[stack.peek()]<temperatures[i]){
Integer index = stack.pop(); //此处i位置的数是第一个大于index位置的元素
result[index] = i - index;
}
stack.push(i);
}
return result;
}
}
496. Next Greater Element I
问题描述
nums1 中数字 x 的 下一个更大元素 是指 x 在 nums2 中对应位置 右侧 的 第一个 比 x 大的元素。
给你两个 没有重复元素 的数组 nums1 和 nums2 ,下标从 0 开始计数,其中nums1 是 nums2 的子集。
对于每个 0 <= i < nums1.length ,找出满足 nums1[i] == nums2[j] 的下标 j ,并且在 nums2 确定 nums2[j] 的 下一个更大元素 。如果不存在下一个更大元素,那么本次查询的答案是 -1 。
返回一个长度为 nums1.length 的数组 ans 作为答案,满足 ans[i] 是如上所述的 下一个更大元素 。
输入输出样例
示例 1:
输入:nums1 = [4,1,2], nums2 = [1,3,4,2].
输出:[-1,3,-1]
解释:nums1 中每个值的下一个更大元素如下所述:
4 ,用加粗斜体标识,nums2 = [1,3,4,2]。不存在下一个更大元素,所以答案是 -1 。
1 ,用加粗斜体标识,nums2 = [1,3,4,2]。下一个更大元素是 3 。
2 ,用加粗斜体标识,nums2 = [1,3,4,2]。不存在下一个更大元素,所以答案是 -1 。
示例 2:
输入:nums1 = [2,4], nums2 = [1,2,3,4].
输出:[3,-1]
解释:nums1 中每个值的下一个更大元素如下所述:
2 ,用加粗斜体标识,nums2 = [1,2,3,4]。下一个更大元素是 3 。
4 ,用加粗斜体标识,nums2 = [1,2,3,4]。不存在下一个更大元素,所以答案是 -1 。
思路
本题是使用单调栈的另一个实例。本质上就是在num2中找到每个数字对应的下一个大于它的数字,可以据此用一个映射表表示。然后在num1中把对应数字的映射值存储为字符串返回。
代码
class Solution {
public int[] nextGreaterElement(int[] nums1, int[] nums2) {
int m = nums1.length;
int n = nums2.length;
int[] result = new int[m];
Map<Integer,Integer> map = new HashMap<>();
Deque<Integer> stack = new LinkedList<>();
for(int i=0;i<n;i++){
while (!stack.isEmpty() && nums2[stack.peek()]<nums2[i]){
Integer index = stack.pop();
map.put(nums2[index],nums2[i]); //存储index的下一个大于它的值
}
stack.push(i);
}
for(int i=0;i<m;i++){
result[i]=map.getOrDefault(nums1[i],-1);
}
return result;
}
}
503. Next Greater Element II
问题描述
给定一个循环数组 nums ( nums[nums.length - 1] 的下一个元素是 nums[0] ),返回 nums 中每个元素的 下一个更大元素 。
数字 x 的 下一个更大的元素 是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1 。
输入输出样例
示例 1:
输入: nums = [1,2,1]
输出: [2,-1,2]
解释: 第一个 1 的下一个更大的数是 2;
数字 2 找不到下一个更大的数;
第二个 1 的下一个最大的数需要循环搜索,结果也是 2。
示例 2:
输入: nums = [1,2,3,4,3]
输出: [2,3,4,-1,4]
思路
仍然是利用单调栈来找下一个较大的值,由于是循环的查找下一个较大值,需要遍历两次数组。
代码
class Solution {
public int[] nextGreaterElements(int[] nums) {
int m = nums.length;
int[] result = new int[m];
Arrays.fill(result,-1);
Deque<Integer> stack = new LinkedList<>();
for(int i=0;i<2*m;i++){
while (!stack.isEmpty() && nums[stack.peek()]<nums[i%m]){
Integer index = stack.pop();
result[index] = nums[i%m];
}
stack.push(i%m);
}
return result;
}
}
1673. Find the Most Competitive Subsequence
问题描述
给你一个整数数组 nums 和一个正整数 k ,返回长度为 k 且最具 竞争力 的 nums 子序列。
数组的子序列是从数组中删除一些元素(可能不删除元素)得到的序列。
在子序列 a 和子序列 b 第一个不相同的位置上,如果 a 中的数字小于 b 中对应的数字,那么我们称子序列 a 比子序列 b(相同长度下)更具 竞争力 。 例如,[1,3,4] 比 [1,3,5] 更具竞争力,在第一个不相同的位置,也就是最后一个位置上, 4 小于 5 。
输入输出样例
示例 1:
输入:nums = [3,5,2,6], k = 2
输出:[2,6]
解释:在所有可能的子序列集合 {[3,5], [3,2], [3,6], [5,2], [5,6], [2,6]} 中,[2,6] 最具竞争力。
示例 2:
输入:nums = [2,4,3,3,5,4,9,6], k = 4
输出:[2,3,3,4]
思路
维护一个单调递增的栈。栈中存放这k个连续最小的数(最具竞争力的子序列),如果栈顶元素大于当前遍历的i元素且数组中剩余元素len-1-i和栈中的元素数量之和(备选的数)大于等于k个时,将栈顶元素出栈。如果栈中的总元素数量小于k,则入栈。
最后只要将栈中的所有元素弹出,即为k个连续最小的数。
细节:当i遍历到后面时,如果备选数量(i后面未遍历的数和栈中的元素数量)不足k时,不将栈顶元素弹出。
代码
class Solution {
public int[] mostCompetitive(int[] nums, int k) {
int m = nums.length;
int[] result = new int[k];
Deque<Integer> stack = new LinkedList<>();
for(int i=0;i<m;i++){
while (!stack.isEmpty() && nums[stack.peek()]>nums[i] && stack.size()+(m-1-i)>=k){
· stack.pop();
}
if(stack.size() < k){
stack.push(i); //将索引入栈
}
}
for(int i=k-1;i>=0;i--){ //反向放入结果中
result[i] = nums[stack.pop()];
}
return result;
}
}
优先队列
23. Merge k Sorted Lists (Hard)
问题描述
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
输入输出样例
示例 1:
输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]
解释:链表数组如下:
[
1->4->5,
1->3->4,
2->6
]
将它们合并到一个有序链表中得到。
1->1->2->3->4->4->5->6
示例 2:
输入:lists = []
输出:[]
思路
可以使用一个优先队列,把所有的链表存储在一个优先队列中,每次提取所有链表头部节点值最小的那个节点,直到所有链表都被提取完为止。
代码
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
Comparator<ListNode> comparator = new Comparator<ListNode>() {
@Override
public int compare(ListNode o1, ListNode o2) {
return Integer.compare(o1.val,o2.val);
}
};
PriorityQueue<ListNode> queue = new PriorityQueue<>(comparator);
for(ListNode list:lists){
ListNode p = list;
while (p!=null){
queue.offer(p);
p = p.next;
}
}
if(queue.isEmpty())return null;
ListNode head = new ListNode(queue.poll().val);
ListNode p = head;
while (!queue.isEmpty()){
p.next = new ListNode(queue.poll().val);
p = p.next;
}
return head;
}
}
239. SlidingWindow Maximum (Hard)
问题描述
给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回 滑动窗口中的最大值 。
输入输出样例
示例 1:
输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
解释:
滑动窗口的位置 最大值
[1 3 -1] -3 5 3 6 7 3
1 [3 -1 -3] 5 3 6 7 3
1 3 [-1 -3 5] 3 6 7 5
1 3 -1 [-3 5 3] 6 7 5
1 3 -1 -3 [5 3 6] 7 6
1 3 -1 -3 5 [3 6 7] 7
示例 2:
输入:nums = [1], k = 1
输出:[1]
思路
思路1:维护一个递减的双向队列,队列最左侧始终最大。窗口向右移动的过程将元素添加到队列中,然后将队列最左侧的值作为当前窗口中的最大值。当窗口滑出最大所在的位置时,将队列从左侧移除不在窗口范围的最大值。
思路2:使用一个递减的优先队列来替换双向队列,简化了维护队列递减的步骤。
代码(思路1)
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
Deque<Integer> queue = new LinkedList<>();
int left=0,right=0;
int n = nums.length;
int[] result = new int[n-k+1];
for(;right<k;right++){
while (!queue.isEmpty() && nums[queue.peekLast()]<nums[right]){
queue.pollLast();
}
queue.offerLast(right);
}
result[0]=nums[queue.peekFirst()];
right--;
for(;left+k<=n;left++,right++){
if(left==0)continue;
if(queue.peekFirst()<left)queue.pollFirst(); //上一个最大值已经不在滑动窗口中
while (!queue.isEmpty() && nums[queue.peekLast()]<nums[right]){
queue.pollLast();
}
queue.offerLast(right);
result[left]=nums[queue.peekFirst()];
}
return result;
}
}
代码(思路2)
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
PriorityQueue<int[]> queue = new PriorityQueue<>(new Comparator<int[]>() {
@Override
public int compare(int[] o1, int[] o2) {
return Integer.compare(o2[1],o1[1]);
}
}); //queue存放[索引,值]
int n = nums.length;
int[] result = new int[n-k+1];
for(int i=0;i<k;i++){
queue.offer(new int[]{i,nums[i]});
}
result[0]=queue.peek()[1];
int left=1,right=left+k-1;
for(;left+k-1<n;left++,right++){
while(!queue.isEmpty() && queue.peek()[0]<left){ //上一个最大值已经不在滑动窗口中
queue.poll();
}
queue.offer(new int[]{right,nums[right]});
result[left]=queue.peek()[1];
}
return result;
}
}
哈希表
1. Two Sum (Easy)
问题描述
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
输入输出样例
示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
示例 2:
输入:nums = [3,2,4], target = 6
输出:[1,2]
思路
可以使用哈希表存储值和索引,然后遍历i时,在哈希表中查找taget-i位置,存在则返回[i,hashmap.get(target-1)]。
代码
class Solution {
public int[] twoSum(int[] nums, int target) {
int m = nums.length;
int[] result = new int[2];
HashMap<Integer,Integer> map = new HashMap<>();
for (int i = 0; i < m; i++) {
if(map.containsKey(target-nums[i])){
result[0]=i;
result[1]=map.get(target-nums[i]);
break;
}
map.put(nums[i],i);
}
return result;
}
}
128. Longest Consecutive Sequence (Hard)
问题描述
给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
请你设计并实现时间复杂度为 O(n) 的算法解决此问题。
输入输出样例
示例 1:
输入:nums = [100,4,200,1,3,2]
输出:4
解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。
示例 2:
输入:nums = [0,3,7,2,5,8,4,6,0,1]
输出:9
思路
首先使用一个哈希表将所有元素放进去, 这一步首先能去除重复元素。之后遍历这些不重复的元素,如果该元素k是一个连续子序列的首元素,我们就从该元素向前查找k+1是否存在,并统计当前子序列长度。最后找出最长的子序列。
代码
class Solution {
public int longestConsecutive(int[] nums) {
HashSet<Integer> set = new HashSet<>();
for(int n:nums){
set.add(n);
}
Iterator<Integer> iterator = set.iterator();
int longest = 0;
while (iterator.hasNext()){
int current_long=1;
int current = iterator.next();
if(!set.contains(current-1)){//剪枝,只从子序列的首元素往后查找,如果不是首元素则跳过查找。不剪枝时间复杂度为O(n^2)
while (set.contains(current+1)){
current_long++;
current++;
}
longest = Math.max(longest,current_long);
}
}
return longest;
}
}
149. Max Points on a Line (Hard)
问题描述
给你一个数组 points ,其中 points[i] = [xi, yi] 表示 X-Y 平面上的一个点。求最多有多少个点在同一条直线上。
示例 1:
输入:points = [[1,1],[2,2],[3,3]]
输出:3
示例 2:
输入:points = [[1,1],[3,2],[5,3],[4,1],[2,3],[1,4]]
输出:4
思路
以一个点作为基准和其他点求斜率,当斜率一致时就可以认为它们在同一条直线上。使用一个哈希表来存储不同斜率上点的个数,之后找到最多个数的作为该基准点上共线最多的点。然后分别以其他点作为基准点寻找每个基准点上最多的点,最后统计各个基准点上最多的共线点。
代码
class Solution {
public int maxPoints(int[][] ps) {
int m = ps.length;
int max_points = 0;
for(int i=0;i<m;i++){
int count = 0;//记录当前基准点做多的共线数,共点数=共线数+1
HashMap<String,Integer> map = new HashMap<>();//使用哈希表记录当前基准点上各斜率共线数
for(int j=i+1;j<m;j++){
int delta_x=ps[i][0]-ps[j][0],delta_y=ps[i][1]-ps[j][1];
int k = gcd(delta_x,delta_y);
String key = delta_x/k+"/"+delta_y/k; //使用最简分数的形式作为斜率,避免斜率浮点数不精确等问题
map.put(key,map.getOrDefault(key,0)+1);
count = Math.max(count,map.get(key));
}
max_points = Math.max(max_points,count+1);//求所有基准点上最大的共点数
}
return max_points;
}
private int gcd(int a,int b){//最大公因数
return b==0?a:gcd(b,a%b);
}
}
前缀和与积分图
303. Range Sum Query - Immutable (Easy)
问题描述
给定一个整数数组 nums,处理以下类型的多个查询:
计算索引 left 和 right (包含 left 和 right)之间的 nums 元素的 和 ,其中 left <= right
实现 NumArray 类:
NumArray(int[] nums) 使用数组 nums 初始化对象
int sumRange(int i, int j) 返回数组 nums 中索引 left 和 right 之间的元素的 总和 ,包含 left 和 right 两点(也就是 nums[left] + nums[left + 1] + … + nums[right] )
输入输出样例
示例 1:
输入:
[“NumArray”, “sumRange”, “sumRange”, “sumRange”]
[[[-2, 0, 3, -5, 2, -1]], [0, 2], [2, 5], [0, 5]]
输出:
[null, 1, -1, -3]
解释:
NumArray numArray = new NumArray([-2, 0, 3, -5, 2, -1]);
numArray.sumRange(0, 2); // return 1 ((-2) + 0 + 3)
numArray.sumRange(2, 5); // return -1 (3 + (-5) + 2 + (-1))
numArray.sumRange(0, 5); // return -3 ((-2) + 0 + 3 + (-5) + 2 + (-1))
思路
使用一个sum数组保存前i项和,则sumRange(left, right)
直接返回sum[right+1]-sum[left]
思路
class NumArray {
int[] sum;
public NumArray(int[] nums) {
int m = nums.length;
sum = new int[m+1];
sum[0]=0;
for (int i = 1; i <= m; i++) {
sum[i]=sum[i-1]+nums[i-1];
}
}
public int sumRange(int left, int right) {
return sum[right+1]-sum[left];
}
}
/**
* Your NumArray object will be instantiated and called as such:
* NumArray obj = new NumArray(nums);
* int param_1 = obj.sumRange(left,right);
*/
304. Range Sum Query 2D - Immutable (Medium)
问题描述
给定一个二维矩阵 matrix,以下类型的多个请求:
计算其子矩形范围内元素的总和,该子矩阵的 左上角 为 (row1, col1) ,右下角 为 (row2, col2) 。
实现 NumMatrix 类:
NumMatrix(int[][] matrix) 给定整数矩阵 matrix 进行初始化
int sumRegion(int row1, int col1, int row2, int col2) 返回 左上角 (row1, col1) 、右下角 (row2, col2) 所描述的子矩阵的元素 总和 。
输入输出样例
示例 1:
输入:
[“NumMatrix”,“sumRegion”,“sumRegion”,“sumRegion”]
[[[[3,0,1,4,2],[5,6,3,2,1],[1,2,0,1,5],[4,1,0,1,7],[1,0,3,0,5]]],[2,1,4,3],[1,1,2,2],[1,2,2,4]]
输出:
[null, 8, 11, 12]
解释:
NumMatrix numMatrix = new NumMatrix([[3,0,1,4,2],[5,6,3,2,1],[1,2,0,1,5],[4,1,0,1,7],[1,0,3,0,5]]);
numMatrix.sumRegion(2, 1, 4, 3); // return 8 (红色矩形框的元素总和)
numMatrix.sumRegion(1, 1, 2, 2); // return 11 (绿色矩形框的元素总和)
numMatrix.sumRegion(1, 2, 2, 4); // return 12 (蓝色矩形框的元素总和)
思路
是一维前缀和的二维扩展,有两点需要注意。
构建前缀图时:sum[i,j]=sum[i-1,j]+sum[i,j-1]-sum[i-1,j-1]+nums[i-1][j-1]
计算中间区域时:result=sum[row2+1][col2+1]-sum[row2+1][col1]-sum[row1][col2+1]+sum[row1][col1]
代码
class NumMatrix {
int[][] sumMatrix;
public NumMatrix(int[][] matrix) {
int m = matrix.length;
int n = matrix[0].length;
sumMatrix = new int[m+1][n+1];
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
sumMatrix[i][j]=sumMatrix[i-1][j]+sumMatrix[i][j-1]-sumMatrix[i-1][j-1]+matrix[i-1][j-1];
}
}
}
public int sumRegion(int row1, int col1, int row2, int col2) {
return sumMatrix[row2+1][col2+1]-sumMatrix[row2+1][col1]-sumMatrix[row1][col2+1]+sumMatrix[row1][col1];
}
}
/**
* Your NumMatrix object will be instantiated and called as such:
* NumMatrix obj = new NumMatrix(matrix);
* int param_1 = obj.sumRegion(row1,col1,row2,col2);
*/
560. Subarray Sum Equals K (Medium)
问题描述
给你一个整数数组 nums 和一个整数 k ,请你统计并返回 该数组中和为 k 的连续子数组的个数 。
输入输出样例
示例 1:
输入:nums = [1,1,1], k = 2
输出:2
示例 2:
输入:nums = [1,2,3], k = 3
输出:2
思路
可以使用一个哈希表来存储子序列每种前缀和的个数,即key为前缀和,value为该前缀和的数量。
和为k的连续子序列的个数=前缀和为sum[i]-k
的子序列的个数。(即有k有多少种长度组合,相应前缀和为sum-k也有多少种长度组合)然后直接在哈希表中查找键值为sum-k的元素,统计所有前缀和为sum-k的个数即可。
代码
class Solution {
public int subarraySum(int[] nums, int k) {
int count=0; //统计和为k的子序列个数
int sum = 0; //前缀和
HashMap<Integer,Integer> hashMap = new HashMap<>();//k存储前缀和,v存储前缀和的个数
hashMap.put(sum,1); //理解为前缀和为0的个数为1
for(int num:nums){
sum+=num;
if(hashMap.containsKey(sum - k)){
count+=hashMap.get(sum-k);
}
hashMap.put(sum,hashMap.getOrDefault(sum,0)+1);
}
return count;
}
}
练习
566. Reshape the Matrix (Easy)
问题描述
在 MATLAB 中,有一个非常有用的函数 reshape ,它可以将一个 m x n 矩阵重塑为另一个大小不同(r x c)的新矩阵,但保留其原始数据。
给你一个由二维数组 mat 表示的 m x n 矩阵,以及两个正整数 r 和 c ,分别表示想要的重构的矩阵的行数和列数。
重构后的矩阵需要将原始矩阵的所有元素以相同的 行遍历顺序 填充。
如果具有给定参数的 reshape 操作是可行且合理的,则输出新的重塑矩阵;否则,输出原始矩阵。
输入输出样例
示例 1:
输入:mat = [[1,2],[3,4]], r = 1, c = 4
输出:[[1,2,3,4]]
示例 2:
输入:mat = [[1,2],[3,4]], r = 2, c = 4
输出:[[1,2],[3,4]]
思路
首先判断输入元素个数和输出元素个数是否相等,如果不相等则说明不能reshape,返回原数组。
在可以reshape的情况,创建一个输出数组,遍历元素组将每个元素按行放置在新数组的对应位置。可以用一个行指针和列指针,列指针到尾则从头再来并将行指针向下移动一位。
代码
class Solution {
public int[][] matrixReshape(int[][] mat, int r, int c) {
int m = mat.length;
int n = mat[0].length;
if(m*n != r*c) return mat;
int[][] newMatrix = new int[r][c];
int row=0,column=0;
for(int i=0;i<m;i++){
for (int j = 0; j < n; j++) {
newMatrix[row][column]=mat[i][j];
column++;
if(column==c){
column=0;
row++;
}
}
}
return newMatrix;
}
}
225. Implement Stack using Queues (Easy)
问题描述
请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。
实现 MyStack 类:
void push(int x) 将元素 x 压入栈顶。
int pop() 移除并返回栈顶元素。
int top() 返回栈顶元素。
boolean empty() 如果栈是空的,返回 true ;否则,返回 false 。
思路
利用两个队列来实现栈,首先入栈时是将该值放在一个不为空的队列后面(初始都为空时随意放一个)。
出栈时,将一个不为空的队列除了队尾的元素,全部出队列放入空的队列中。然后再将队尾只剩一个的队列出队列。
代码
class MyStack {
Deque<Integer> queue1;
Deque<Integer> queue2;
public MyStack() {
queue1 = new LinkedList<>();
queue2 = new LinkedList<>();
}
public void push(int x) {
if(queue1.isEmpty() && queue2.isEmpty()) queue1.offerLast(x);
else if(queue1.isEmpty()) queue2.offerLast(x);
else queue1.offerLast(x);
}
public int pop() {
int tem;
if(queue1.isEmpty()){
while (queue2.size()>1){
tem = queue2.pollFirst();
queue1.offerLast(tem);
}
return queue2.pollFirst();
}else {
while (queue1.size()>1){
tem = queue1.pollFirst();
queue2.offerLast(tem);
}
return queue1.pollFirst();
}
}
public int top() {
if(queue1.isEmpty()) return queue2.peekLast();
return queue1.peekLast();
}
public boolean empty() {
return queue1.isEmpty() && queue2.isEmpty();
}
}
/**
* Your MyStack object will be instantiated and called as such:
* MyStack obj = new MyStack();
* obj.push(x);
* int param_2 = obj.pop();
* int param_3 = obj.top();
* boolean param_4 = obj.empty();
*/
217. Contains Duplicate (Easy)
问题描述
给你一个整数数组 nums 。如果任一值在数组中出现 至少两次 ,返回 true ;如果数组中每个元素互不相同,返回 false 。
输入输出样例
示例 1:
输入:nums = [1,2,3,1]
输出:true
示例 2:
输入:nums = [1,2,3,4]
输出:false
思路
使用一个哈希表来存储,如果有重复的元素则只存一个。这样,最后比较哈希表的有效长度和元素组长度,长度一致时说明没有重复元素。
代码
class Solution {
public boolean containsDuplicate(int[] nums) {
Set<Integer> set = new HashSet<>();
for(int num:nums){
set.add(num);
}
if(nums.length == set.size()) return false;
return true;
}
}
优化了一点点
class Solution {
public boolean containsDuplicate(int[] nums) {
Set<Integer> set = new HashSet<>();
for(int num:nums){
if(!set.add(num)) return true;//如果有相同元素,则add返回false
}
return false;
}
}
697. Degree of an Array (Easy)
问题描述
给定一个非空且只包含非负数的整数数组 nums,数组的 度 的定义是指数组里任一元素出现频数的最大值。
你的任务是在 nums 中找到与 nums 拥有相同大小的度的最短连续子数组,返回其长度。
输入输出样例
示例 1:
输入:nums = [1,2,2,3,1]
输出:2
解释:
输入数组的度是 2 ,因为元素 1 和 2 的出现频数最大,均为 2 。
连续子数组里面拥有相同度的有如下所示:
[1, 2, 2, 3, 1], [1, 2, 2, 3], [2, 2, 3, 1], [1, 2, 2], [2, 2, 3], [2, 2]
最短连续子数组 [2, 2] 的长度为 2 ,所以返回 2 。
示例 2:
输入:nums = [1,2,2,3,1,4,2]
输出:6
解释:
数组的度是 3 ,因为元素 2 重复出现 3 次。
所以 [2,2,3,1,4,2] 是最短子数组,因此返回 6 。
思路
本题题意就是找到众数(可能有多个)所在的最短的子序列。
思路为:使用一个哈希映射表,key存放元素,value存放该元素对应的元素(第一出现位置,最后一次出现位置,出现次数),在构建哈希映射表过程中,寻找众数。这样,再遍历一次哈希映射表,找到众数最后一次出现位置-第一次出现位置+1最短的一项即为结果。
代码
class Solution {
public int findShortestSubArray(int[] nums) {
HashMap<Integer,int[]> map = new HashMap<>();//key为某元素,value为(第一次出现位置,最后一次出现位置,出现次数)
int n = nums.length;
int mostCount=0;
for (int i = 0; i < n; i++) {
if(!map.containsKey(nums[i])){
//第一次放该值进哈希映射表,初始化value值
map.put(nums[i],new int[]{i,i,1});
}else {
//已经有该值,更新最后一次出现位置以及出现次数
int[] position = map.get(nums[i]);
position[1]=i;
position[2]++;
}
//保存众数
mostCount = Math.max(mostCount,map.get(nums[i])[2]);
}
Set<Map.Entry<Integer, int[]>> entries = map.entrySet();
Iterator<Map.Entry<Integer, int[]>> iterator = entries.iterator();
int minDistance = Integer.MAX_VALUE/2;
while (iterator.hasNext()){
Map.Entry<Integer, int[]> current = iterator.next();
if(current.getValue()[2]==mostCount){
minDistance=Math.min(minDistance,current.getValue()[1]-current.getValue()[0]+1);
}
}
return minDistance;
}
}
594. Longest Harmonious Subsequence (Easy)
问题描述
和谐数组是指一个数组里元素的最大值和最小值之间的差别 正好是 1 。
现在,给你一个整数数组 nums ,请你在所有可能的子序列中找到最长的和谐子序列的长度。
数组的子序列是一个由数组派生出来的序列,它可以通过删除一些元素或不删除元素、且不改变其余元素的顺序而得到。
输入输出样例
示例 1:
输入:nums = [1,3,2,2,5,2,3,7]
输出:5
解释:最长的和谐子序列是 [3,2,2,2,3]
示例 2:
输入:nums = [1,2,3,4]
输出:2
思路
就是在原数组的中找相邻大小的元素序列,然后找所有满足的子序列最大长度。
可以用一个哈希映射表存储各元素的数量。之后遍历哈希表,如果num和num+1都存在,则统计它们一共有多少个,如此循环找到最大的num和num+1的数量。
代码
class Solution {
public int findLHS(int[] nums) {
int longest = 0;
HashMap<Integer,Integer> map = new HashMap<>();// value保存出现的次数
for (int num:nums) {
map.put(num,map.getOrDefault(num,0)+1);
}
for (int key:map.keySet()) {
if(map.containsKey(key+1)){
longest=Math.max(longest,map.get(key)+map.get(key+1));
}
}
return longest;
}
}
287. Find the Duplicate Number (Medium)
问题描述
给定一个包含 n + 1 个整数的数组 nums ,其数字都在 [1, n] 范围内(包括 1 和 n),可知至少存在一个重复的整数。
假设 nums 只有 一个重复的整数 ,返回 这个重复的数 。
你设计的解决方案必须 不修改 数组 nums 且只用常量级 O(1) 的额外空间。
输入输出样例
示例 1:
输入:nums = [1,3,4,2,2]
输出:2
示例 2:
输入:nums = [3,1,3,4,2]
输出:3
思路
该题目限制条件:空间复杂度为O(1),时间复杂度为O(n),且 n + 1 个整数的数组 其数字都在 [1, n] 范围内。
于是,可以利用元素构造一个链表路径:即下一个元素为以当前元素为索引的元素。(如果当前元素为3,则下一个查找num[3]元素)。由于有重复元素存在,所以这个链表会产生一个回路。即该问题转换为找到有回路链表的入口。
回忆方法:使用快慢指针,慢指针一次走一步,快指针一次走两步,这样找到当他们相遇的位置。再将快指针变为慢指针并指在初始位置,两个慢指针继续向前,再次相遇时就是回路的入口。
代码
class Solution {
public int findDuplicate(int[] nums) {
int slow=0,fast=0;
do{
slow=nums[slow];
fast=nums[nums[fast]]; //快指针
}while (slow!=fast);
slow=0;
while (slow!=fast){
slow=nums[slow];
fast=nums[fast];
}
return slow;
}
}
264. Ugly Number II
问题描述
给你一个整数 n ,请你找出并返回第 n 个 丑数 。
丑数 就是只包含质因数 2、3 和/或 5 的正整数。
输入输出样例
示例 1:
输入:n = 10
输出:12
解释:[1, 2, 3, 4, 5, 6, 8, 9, 10, 12] 是由前 10 个丑数组成的序列。
示例 2:
输入:n = 1
输出:1
解释:1 通常被视为丑数。
思路
使用一个优先队列来维护从小到大的规律,使用一个哈希表来过滤重复元素。首先将1作为丑数放入队列中,之后每次从队列中取出一个值x,其2x,3x,5x都是丑数,然后分别将它们用哈希表过滤放入优先队列中。当从队列中取出第n个丑数时,就是返回值。
class Solution {
public int nthUglyNumber(int n) {
Queue<Long> queue = new PriorityQueue<>();
Set<Long> set = new HashSet<>();
queue.offer(1L);
set.add(1L);
long num=0;
for(int i=0;i<n;i++){
num = queue.poll();
if(set.add(num*2)){
queue.offer(num*2);
}
if(set.add(num*3)){
queue.offer(num*3);
}
if(set.add(num*5)){
queue.offer(num*5);
}
}
return (int) num;
}
}
313. Super Ugly Number (Medium)
问题描述
超级丑数 是一个正整数,并满足其所有质因数都出现在质数数组 primes 中。
给你一个整数 n 和一个整数数组 primes ,返回第 n 个 超级丑数 。
题目数据保证第 n 个 超级丑数 在 32-bit 带符号整数范围内。
输入输出样例
示例 1:
输入:n = 12, primes = [2,7,13,19]
输出:32
解释:给定长度为 4 的质数数组 primes = [2,7,13,19],前 12 个超级丑数序列为:[1,2,4,7,8,13,14,16,19,26,28,32] 。
示例 2:
输入:n = 1, primes = [2,3,5]
输出:1
解释:1 不含质因数,因此它的所有质因数都在质数数组 primes = [2,3,5] 中。
思路
和上一题丑数的思路类似,只是把2,3,5换成了primes数组。同样的方法再做时,如果primes数组过长,就相当于n叉树,非常占用内存,在最新的LeetCode样例中会超时。
另外官方应该推荐dp来做…【后面再补】
代码(超内存限制)
class Solution {
public int nthSuperUglyNumber(int n, int[] primes) {
Queue<Long> queue = new PriorityQueue<>();
queue.offer(1L);
long num=0;
for(int i=0;i<n;i++){
num = queue.poll();
//此处省略了哈希表占用内存,还是不行...
while(!queue.isEmpty()&&num==queue.peek()) queue.poll();
for(int prime:primes){
queue.offer(num*prime);
}
}
return (int)num;
}
}
870. Advantage Shuffle (Medium)
问题描述
给定两个大小相等的数组 nums1 和 nums2,nums1 相对于 nums 的优势可以用满足 nums1[i] > nums2[i] 的索引 i 的数目来描述。
返回 nums1 的任意排列,使其相对于 nums2 的优势最大化。
输入输出样例
示例 1:
输入:nums1 = [2,7,11,15], nums2 = [1,10,4,11]
输出:[2,11,7,15]
示例 2:
输入:nums1 = [12,24,8,32], nums2 = [13,25,32,11]
输出:[24,32,8,12]
思路
这是一道核心为田忌赛马的问题,思路为:向将两个数组进行排序,从大的一端开始比较,如果己方最大值大于对方最大值,使用一个键值存放对方的值的原索引和己方当前最大值,如果己方当前最大值小于对方当前最大值,哈希表就存放对方值的索引与己方最小值。如此,哈希表中就存放好了对方每张牌对应我方的牌。
代码
class Solution {
public int[] advantageCount(int[] nums1, int[] nums2) {
int n = nums1.length;
Map<Integer,Integer> map = new HashMap<>();
int[][] position = new int[n][2];
for(int i=0;i<n;i++){
position[i]=new int[]{nums2[i],i};
}
//从小到大排列
Comparator<int[]> comparator1 = (int[] o1, int[] o2)->{
return Integer.compare(o1[0],o2[0]);
};
Arrays.sort(nums1);
Arrays.sort(position,comparator1);
int high1=n-1,high2=n-1;
int low1=0;
for(;high2>=0;high2--){
if(nums1[high1]>position[high2][0]){
map.put(position[high2][1],nums1[high1]);
high1--;
}else {
map.put(position[high2][1],nums1[low1]);
low1++;
}
}
for (int i = 0; i < n; i++) {
nums1[i]=map.get(i);
}
return nums1;
}
}
307. Range Sum Query - Mutable (Medium)
问题描述
给你一个数组 nums ,请你完成两类查询。
其中一类查询要求 更新 数组 nums 下标对应的值
另一类查询要求返回数组 nums 中索引 left 和索引 right 之间( 包含 )的nums元素的 和 ,其中 left <= right
实现 NumArray 类:
NumArray(int[] nums)
用整数数组 nums 初始化对象
void update(int index, int val)
将 nums[index] 的值 更新 为 val
int sumRange(int left, int right)
返回数组 nums 中索引 left 和索引 right 之间( 包含 )的nums元素的和 (即,nums[left] + nums[left + 1], ..., nums[right]
)
思路
最简单的思路是,当更新某个位置的值时,计算差值diff,然后sum从i的位置开始依次增加diff。
优化思路:通过线段树去做(留个坑……)
代码
class NumArray {
int[] sum;
int[] nums;
public NumArray(int[] nums) {
this.nums = nums;
int m = nums.length;
sum = new int[m+1];
sum[0]=0;
for (int i = 1; i <= m; i++) {
sum[i]=sum[i-1]+nums[i-1];
}
}
public void update(int index, int val) {
int diff = val - nums[index];
nums[index]=val;
for(int i=index+1;i<sum.length;i++){
sum[i]+=diff;
}
}
public int sumRange(int left, int right) {
return sum[right+1]-sum[left];
}
}
/**
* Your NumArray object will be instantiated and called as such:
* NumArray obj = new NumArray(nums);
* obj.update(index,val);
* int param_2 = obj.sumRange(left,right);
*/