队列:先进先出
假设固定大小为len,size为当前队列大小,start表示队头,end表示队尾。
栈:先进后出
假设固定大小为len, index 表示数组位置
设计一个支持 push,pop,top 操作,并能在常数时间内检索到最小元素的栈。
示例:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin(); --> 返回 -3.
minStack.pop();
minStack.top(); --> 返回 0.
minStack.getMin(); --> 返回 -2.
解答:
设计两个栈:data 和 min
data 代表当前栈,min 保存当前栈的最小元素。
进栈时:比较当前元素 x 与 min 栈顶元素 top
出栈时:data 和 min 同时弹出栈顶元素。
mini 的栈顶元素即为 data 栈中的最小元素。
JAVA代码
class MinStack {
/** initialize your data structure here. */
private Stack<Integer> stackData;
private Stack<Integer> stackMin;
public MinStack() {
stackData = new Stack<Integer>();
stackMin = new Stack<Integer>();
}
public void push(int x) {
stackData.push(x);
if (!stackMin.isEmpty()) {
if (x < stackMin.peek()) {
stackMin.push(x);
}
else stackMin.push(stackMin.peek());
}
else{
stackMin.push(x);
}
}
public void pop() {
stackData.pop();
stackMin.pop();
}
public int top() {
return stackData.peek();
}
public int getMin() {
return stackMin.peek();
}
}
/**
使用队列实现栈的下列操作:
注意:
解答:
用两个队列实现栈结构。
队列:先进先出
栈:先进后出
使用两个队列:data 和 help
出栈:
入栈:直接入队列 data.
JAVA代码:
class MyStack {
private Queue<Integer> data;
private Queue<Integer> help;
/** Initialize your data structure here. */
public MyStack() {
data = new LinkedList<Integer>();
help = new LinkedList<Integer>();
}
/** Push element x onto stack. */
public void push(int x) {
data.add(x);
}
/** Removes the element on top of the stack and returns that element. */
public int pop() {
while (data.size()>1){
help.add(data.remove());
}
int top = data.remove();
Queue<Integer> temp = data;
data = help;
help = temp;
return top;
}
/** Get the top element. */
public int top() {
while (data.size()>1){
help.add(data.remove());
}
int top = data.peek();
help.add(data.remove());
Queue<Integer> temp = data;
data = help;
help = temp;
return top;
}
/** Returns whether the stack is empty. */
public boolean empty() {
if (data.isEmpty()){
return true;
}
else return false;
}
}
/**
* 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();
*/
使用栈实现队列的下列操作:
示例:
MyQueue queue = new MyQueue();
queue.push(1);
queue.push(2);
queue.peek(); // 返回 1
queue.pop(); // 返回 1
queue.empty(); // 返回 false
说明:
解答:
用两个栈结构:push 和 pop
JAVA代码:
class MyQueue {
private Stack<Integer> push;
private Stack<Integer> pop;
/** Initialize your data structure here. */
public MyQueue() {
push = new Stack<Integer>();
pop = new Stack<Integer>();
}
/** Push element x to the back of queue. */
public void push(int x) {
push.push(x);
}
/** Removes the element from in front of queue and returns that element. */
public int pop() {
if (pop.empty()){
while (!push.empty()){
pop.push(push.pop());
}
}
return pop.pop();
}
/** Get the front element. */
public int peek() {
if (pop.empty()){
while (!push.empty()){
pop.push(push.pop());
}
}
return pop.peek();
}
/** Returns whether the queue is empty. */
public boolean empty() {
if (push.empty() && pop.empty()){
return true;
}
else return false;
}
}
/**
* 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();
*/
给定一个正整数 n,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的正方形矩阵。
示例:
输入: 3
输出:
[
[ 1, 2, 3 ],
[ 8, 9, 4 ],
[ 7, 6, 5 ]
]
题解:
T,B,L,R分别表示未扫描矩阵的边界。
初始时,T=0,B=n-1,L=0,R=n-1; 直到整个矩阵扫完完毕结束。
先从左到右行扫描,第T行的第L列到第R列,即nums[T][L]~nums[T][R];扫描完一行,相应T增1.
从上到下列扫描,第R列的第T行到第B行,即nums[T][R]~nums[B][R];扫描完一列,相应R减1.
从右到左行扫描,第B行的第R列到第L列,即nums[B][R]~nums[B][L];扫描完一行,相应B减1.
从下到上列扫描,第L列的第B行到第T行,即nums[B][L]~nums[T][L];扫描完一列,相应L增1.
JAVA代码:
class Solution {
public int[][] generateMatrix(int n) {
/*
从左到右一行
从上到下一列
从右到左一行
从下到上一列
*/
int[][] res = new int[n][n];
int num = 1;
int l = 0;
int r= n-1;
int t = 0;
int b = n-1;
int i=0;
while(num<=n*n){
//从左到右
for (i=l; i<=r; i++) res[t][i] = num++;
t++;
//从上到下
for (i=t; i<=b; i++) res[i][r] = num++;
r--;
//从右到左
for (i=r; i>=l; i--) res[b][i] = num++;
b--;
//从下到上
for (i=b; i>=t; i--) res[i][l] = num++;
l++;
}
return res;
}
}
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/rotate-matrix-lcci
给定一幅由N × N矩阵表示的图像,其中每个像素的大小为4字节,编写一种方法,将图像旋转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]
]
第 i 行与第 n-i-1 列对应元素交换,而后上下翻转.
解答:
JAVA代码
class Solution {
public void rotate(int[][] matrix) {
//先交换
int size = matrix.length;
for (int i=0; i<size; i++){
for (int j=0; j<size-i-1; j++){
matrix[i][j] = matrix[i][j]^matrix[size-j-1][size-i-1];
matrix[size-j-1][size-i-1] = matrix[i][j]^matrix[size-j-1][size-i-1];
matrix[i][j] = matrix[i][j]^matrix[size-j-1][size-i-1];
}
}
//再上下翻转
for (int i=0; i<size/2; i++){
for (int j=0; j<size; j++){
matrix[i][j] = matrix[i][j]^matrix[size-i-1][j];
matrix[size-i-1][j] = matrix[i][j]^matrix[size-i-1][j];
matrix[i][j] = matrix[i][j]^matrix[size-i-1][j];
}
}
}
}
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/reverse-linked-list
反转一个单链表。
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
进阶:
你可以迭代或递归地反转链表。你能否用两种方法解决这道题?
解答:
迭代版
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
ListNode curr = head;
ListNode prev = null;
ListNode temp = null;
while (curr!=null){
temp = curr.next;
curr.next = prev;
prev = curr;
curr = temp;
}
return prev;
}
}
递归版
class Solution {
public ListNode reverseList(ListNode head) {
if (head==null || head.next==null){
return head;
}
ListNode res = reverseList(head.next); //反转链表head.next
//最后将head反转
head.next.next = head;
head.next = null;
return res;
}
}
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/diagonal-traverse
给定一个含有 M x N 个元素的矩阵(M 行,N 列),请以对角线遍历的顺序返回这个矩阵中的所有元素,对角线遍历如下图所示。
示例:
输入:
[
[ 1, 2, 3 ],
[ 4, 5, 6 ],
[ 7, 8, 9 ]
]
输出: [1,2,4,7,5,3,6,8,9]
解释:
说明:
给定矩阵中的元素总数不会超过 100000 。
题解:
设计宏观结构:
设置A表示右上的位置,B表示左下的位置。
每次,设置A往右走一步,到最右了再往下走;同时B往下走,到最下了再往右走;
扫描从左下B走到右上A的元素,或者从右上A走到左下B的元素。
当A和B同时走到最后一个元素位置时遍历结束。
JAVA代码
class Solution {
public int[] findDiagonalOrder(int[][] matrix) {
//判空
if (matrix == null || matrix.length == 0) {
return new int[0];
}
//矩阵M行,N列
int M = matrix.length;
int N = matrix[0].length;
int[] res = new int[M*N];
//点A:matrix[aRow][aCol], 点B: matrix[bRow][bRow]
int aRow = 0;
int aCol = 0;
int bRow = 0;
int bCol = 0;
res[0] = matrix[0][0]; //先把第一个元素装进去
int k = 1; //k为res的下标
int flag = 1; //立个flag,当flag为奇数: A->B; 当flag为偶数:B->A
while (aRow < M && bRow < M && aCol < N && bCol < N){
//A每次往右走一步,到最右再往下走
//B每次往下走一步,到最下再往右走
aRow = aCol == N-1? aRow+1: aRow;
aCol = aCol < N-1? aCol+1: aCol;
bCol = bRow == M-1? bCol+1: bCol;
bRow = bRow < M-1? bRow+1: bRow;
//从左下B向右上A方向扫描
//或从右上A向左下B方向扫描
// A->B
if (flag %2 == 1){
int i = aRow;
int j = aCol;
while (i<=bRow && j>=bCol){
res[k++] = matrix[i++][j--];
}
flag++;
}
// B->A
else if(flag%2 == 0){
int i = bRow;
int j = bCol;
while (i>=aRow && j<=aCol){
res[k++] = matrix[i--][j++];
}
flag++;
}
}
return res;
}
}
【题目】 给定一个有N*M的整型矩阵matrix和一个整数K, matrix的每一行和每一 列都是排好序的。实现一个函数,判断K 是否在matrix中。 例如: 0 1 2 5 2 3 4 7 4 4 4 8 5 7 7 9 如果K为7,返回true;如果K为6,返 回false。 【要求】 时间复杂度为O(N+M),额外空间复杂度为O(1)。
解答:
从后向前找。
【题目】: 给定两个有序链表的头指针head1,和head2,打印两个链表的公共部分.
和归并排序中merge过程类似
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/palindrome-linked-list
请判断一个链表是否为回文链表。
示例 1:
输入: 1->2
输出: false
示例 2:
输入: 1->2->2->1
输出: true
进阶:
你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?
解答:
思路一:
考虑到栈结构出栈时是逆序的,因此借助栈。
扫描一遍链表,并将数值存放到栈中;
再扫描一遍链表,每扫描一个结点查看是否与栈顶元素相等,并弹出一个元素。若每次都相等,则是回文链表。
空间复杂度O(n)
思路二:
链表后半段入栈,并将链表前半段与栈比较。
(找中点的方式,快指针一次走两步,慢指针一次走一步)
空间复杂度O(n/2)
思路三:
链表后半段逆序,比较两个链表是否相等。
链表后半段再次还原回来。
注意的地方:
首先不管链表结点个数是奇数个还是偶数个,后部分头结点都是p1后一个结点,因此p1确定了,后半部分就确定了。
其次当链表结点为奇数个时,那么前半部分链表长度比后半部分多1,但在比对时,不用考虑前半部分最后一个结点,只要后半部分链表指针到了结尾,那么就算比对成功。
时间复杂度O(n+n/2) = O(n);
只用到了常数个指针,因此空间复杂度为O(1).
JAVA代码
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public boolean isPalindrome(ListNode head) {
//定义一个快指针和一个慢指针
ListNode p1 = head;
ListNode p2 = head;
if (head == null || head.next == null) return true;
//找到中点,p1指向中点, p2指向中点的后面一个结点
while(p2.next!=null && p2.next.next!=null){
p1 = p1.next;
p2 = p2.next.next;
}
p2 = p1.next;
p1.next = null;
//反转链表后半部分,rev为反转后的链表
ListNode p3 = null;
while (p2!=null){
ListNode temp = p2.next;
p2.next = p3;
p3 = p2;
p2 = temp;
}
ListNode rev = p3;
//比较两个链表
p1 = head;
while(p3!=null){
if ( p1.val == p3.val){
p1 = p1.next;
p3 = p3.next;
}
else break;
}
if (p3 == null){
return true;
}else{
return false;
}
}
}
【题目】:
给定一个单项链表的头结点head,节点的值类型时整形,再给定一个整数pivot。实现一个调整链表的函数,左半部分小于pivot,中间部分等于,右半部分大于。但是对于调整后的节点顺序没有更多的要求。
例如:
9->0->4->5->1,pivot=3
结果可以为1->0->4->9->5,也可以为0->1->9->5->4
在左、中、右三个部分的内部也做顺序要求,要求每部分里的节点从左到右的顺序与原链表中节点的先后次序一致。
例如:链表9->0->4->5->1,pivot=3。
调整后的链表是0->1->9->4->5。
在满足原问题要求的同时,左部分节点从左到右为0、1。在原链表中也 是先出现0,后出现1;中间部分在本例中为空,不再讨论;
右部分节点 从左到右为9、4、5。在原链表中也是先出现9,然后出现4,最后出现5。
如果链表长度为N,时间复杂度请达到O(N),额外空间复杂度请达到O(1)。
解答:
定义三个结点less、equal、more
遍历链表:
当前结点值小于num,则将结点接在less后面
当前结点值等于num,则将结点接在equal后面
当前结点等于num,则将结点接在more后面
最后将less、equal 和 more 拼接.
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/copy-list-with-random-pointer
给定一个链表,每个节点包含一个额外增加的随机指针,该指针可以指向链表中的任何节点或空节点。
要求返回这个链表的 深拷贝。
我们用一个由 n
个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index]
表示:
val:一个表示 Node.val 的整数。
random_index:随机指针指向的节点索引(范围从 0 到 n-1);如果不指向任何节点,则为 null 。
示例 :
输入:head = [[3,null],[3,0],[3,null]]
输出:[[3,null],[3,0],[3,null]]
提示:
-10000 <= Node.val <= 10000
Node.random 为空(null)或指向链表中的节点。
节点数目不超过 1000 。
解答:
将每个拷贝节点都作为原来节点下一个结点。
新节点的随机指针指向 原结点随机指针指向的结点的下一个结点。
最后将原结点删除。
JAVA代码
/*
// Definition for a Node.
class Node {
int val;
Node next;
Node random;
public Node(int val) {
this.val = val;
this.next = null;
this.random = null;
}
}
*/
class Solution {
public Node copyRandomList(Node head) {
//判空
if (head == null) return null;
//复制结点插入原链表
Node p = head;
while(p!=null){
Node copyP = new Node(p.val);
copyP.next = p.next;
p.next = copyP;
p = copyP.next;
}
//复制节点加上random指针
p = head;
Node copyP = head.next;
while (p!=null){ //原链表某结点的random指向为空,则复制结点的random结点也指向空
p.next.random = p.random!=null? p.random.next: null;
p = p.next.next;
}
//调整next指针
p = head;
Node headCopy = head.next;
copyP = headCopy;
while (copyP.next != null){
p.next = copyP.next;
p = copyP.next;
copyP.next = p.next;
copyP = p.next;
}
p.next = null;
return headCopy;
}
}
【题目】:
单链表可能有环,也可能无环。给定两个单链表的头节点head1和head2,这两个链表可能相交,也可能不相交。请实现一个函数,如果两个链表相交,请返回相交的第一个节点;如果不相交,返回null。
要求:
如果链表1的长度为N,链表2的长度为M,时间复杂度请达到O(N+M),额外空间复杂度请达到O(1)。
【问题一】判断链表是否有环
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/linked-list-cycle
给定一个链表,判断链表中是否有环。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
解答:
//哈希表法
public class Solution {
public boolean hasCycle(ListNode head) {
ListNode p = head;
Set<ListNode> nodes = new HashSet<>();
while(p!=null){
if (nodes.contains(p)){
return true;
}
else {
nodes.add(p);
}
p = p.next;
}
return false;
}
}
JAVA代码
public class Solution {
public boolean hasCycle(ListNode head) {
if (head == null || head.next == null) return false;
ListNode fast = head.next;
ListNode slow = head;
while(fast!=slow){
if (fast == null || fast.next==null){
return false;
}
fast = fast.next.next;
slow = slow.next;
}
return true;
}
}
【问题二】无环链表相交
JAVA代码
//哈希表法
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
Set<ListNode> nodes = new HashSet<>();
//链表A节点装进哈希表
ListNode currA = headA;
while (currA!=null){
nodes.add(currA);
currA = currA.next;
}
//查看链表B节点在哈希表中是否存在
ListNode currB = headB;
while (currB!=null){
if (nodes.contains(currB)){
return currB;
}
currB = currB.next;
}
return null;
}
}
若相等,则两个链表相交,(但最后一个节点不一定是第一个相交的节点)。
最后较长的长链表从头开始走|len1-len2|步。此时剩余长度与短链表长度相等;
短链表从头与长链表剩余部分同步向后走,一定会走到两个链表相交的节点处。
JAVA代码
//双指针法
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
//判空
if (headA==null || headB==null) return null;
//遍历表A, 统计长度并获得最后一个节点
int lenA = 0;
ListNode currA = headA;
while (currA.next!=null){
lenA++;
currA = currA.next;
}
lenA++;
//遍历表B, 统计长度并获得最后一个节点
int lenB = 0;
ListNode currB = headB;
while (currB.next!=null){
lenB++;
currB = currB.next;
}
lenB++;
//若有交点,最后一个节点相等
if (currA==currB){
//使A为长链表
if (lenA<lenB){
ListNode temp = headA;
headA = headB;
headB = temp;
}
int len = Math.abs(lenA-lenB);
//长链表先走len步
currA = headA;
for (int i = len; i>0; i--){
currA = currA.next;
}
currB = headB;
//两个链表同步向后走
while(currA!=currB){
currA = currA.next;
currB = currB.next;
}
return currA;
}
return null;
}
}
【问题三】有环链表相交
解答:
只存在三种情形的拓扑结构,如图。
首先找到两个链表各自的入环点 loop1 和 loop2。