笔记01
_11_返回倒数第k个节点
package LeetCode._面试经典.笔记01;
public class _11_返回倒数第k个节点 {
public class ListNode {
int val;
ListNode next;
ListNode(int x) { val = x; }
}
/*
1.递归:代码简洁但是需要占用O(N)的空间
2.迭代:代码稍微复杂一点但是效率快
* */
public int K=0;
public int value=0;
public int kthToLast(ListNode head, int k) {
K=k;
nthToLast(head);
return value;
}
//递归每次返回该节点的下标(tip:最后1个为倒数第1个)
public int nthToLast(ListNode node){
if (node==null) return 0;
int l=nthToLast(node.next)+1;
if (l==K) value=node.val;
return l;
}
//快慢指针
public int kthToLast02(ListNode head, int k) {
if (head==null) return 0;
ListNode slow=head;
ListNode fast=head;
while (k>0&&fast!=null){//最后fast肯定到null所以需要多走1步
fast=fast.next;
k--;
}
if (k!=0) return 0;//不合法的k
while (fast!=null){
slow=slow.next;
fast=fast.next;
}
return slow.val;
}
}
_12_删除链表中间节点
package LeetCode._面试经典.笔记01;
import org.junit.Test;
import java.util.Random;
public class _12_删除链表中间节点 {
public class ListNode {
int val;
ListNode next;
ListNode(int x) { val = x; }
}
/*
中间节点:除去首尾节点外都叫中间节点,只能访问删除的的节点
tips:若待删除的是尾结点,该题无解,面试的时候应该给面试官指出
将其为null ? 很明显这就是对指针的不理解了,你置null只是将node置null,却并未将前驱节点的next置null,其还是只想该块内存额
* */
public void deleteNode(ListNode node) {
if (node==null) return;
if (node.next==null) {
System.out.println("尾结点:"+node.val);
node.val=-1;
return;
}
node.val=node.next.val;
node.next=node.next.next;
}
@Test
public void test(){
//[a,b] random(b-a)+a
ListNode head=new ListNode(1);
ListNode n2=new ListNode(2);
ListNode n3=new ListNode(3);
ListNode n4=new ListNode(4);
head.next=n2;
n2.next=n3;
n3.next=n4;
deleteNode(n4);
print(head);
}
public void print(ListNode node){
ListNode p=node;
while (p!=null){
System.out.println(p.val);
p=p.next;
}
}
}
_13_分割链表
package LeetCode._面试经典.笔记01;
import org.junit.Test;
public class _13_分割链表 {
public class ListNode {
int val;
ListNode next;
ListNode(int x) { val = x; }
}
/*
询问如果不考虑"稳定性":原来的顺序不变-->1个链表:头插+尾插
考虑:2个链表
* */
public ListNode partition01(ListNode head, int x) {
if (head==null) return null;
ListNode lowS=new ListNode(-1);
ListNode lowL=lowS;
ListNode highS=new ListNode(-1);
ListNode highL=highS;
ListNode p=head;
while (p!=null){//保证losS或highS最后1个不为null
if (p.val<x){
lowL.next=p;
lowL=lowL.next;
}
else {
highL.next=p;
highL=highL.next;
}
p=p.next;
}
//避免出现环
lowL.next=null;
highL.next=null;
lowL.next=highS.next;
return lowS.next;
}
//不考虑稳定性,利用现有节点进行头插尾插法,更简洁代码
//左边部分<x 右边部分>=x
public ListNode partition(ListNode head, int x){
if (head==null) return null;
ListNode S=head;
ListNode L=head;
ListNode P=head.next;
while (P!=null){
ListNode next=P.next;
if (P.val<x){
P.next=S;//p.next断开,下面就用不到了
S=P;
}
else {
L.next=P;
L=P;
}
P=next;
}
L.next=null;
return S;
}
@Test
public void test(){
ListNode n1=new ListNode(1);
ListNode n2=new ListNode(4);
ListNode n3=new ListNode(3);
ListNode n4=new ListNode(2);
ListNode n5=new ListNode(5);
ListNode n6=new ListNode(2);
n1.next=n2;
n2.next=n3;
n3.next=n4;
n4.next=n5;
n5.next=n6;
ListNode res=partition(n1,3);
}
}
_14_链表求和
package LeetCode._面试经典.笔记01;
public class _14_链表求和 {
public class ListNode {
int val;
ListNode next;
ListNode(int x) { val = x; }
}
/*
首先回顾一下加法:
6 1 7
+ 2 5 9
可用递归实现,注意短链表的空指针
* */
public ListNode addTwoNumbers01(ListNode l1, ListNode l2) {
return nextList(l1,l2,0);
}
public ListNode nextList(ListNode l1,ListNode l2,int carry){
if (l1==null&&l2==null&&carry==0) return null;
int sum=carry;
if (l1!=null) sum+=l1.val;
if (l2!=null) sum+=l2.val;
ListNode res=new ListNode(sum%10);//先序遍历
res.next=nextList(l1==null?null:l1.next,l2==null?null:l2.next,sum/10);
return res;
}
/*
进阶:整数正向存储
因为需要配对,所以需要先补0
* */
public class PartialSum{//封装类
public ListNode sum=null;
public int carry=0;
}
//两个节点相加得到一个封装加数节点PartialSum,再往前传递即可
PartialSum addHelper(ListNode l1,ListNode l2){
if (l1==null&&l2==null){
return new PartialSum();
}
PartialSum sumHelper=addHelper(l1.next,l2.next);
//得到l1.next,l2.next的加数类,直接拿加数类的节点即可
//构造当前加数类的节点
int val=sumHelper.carry+l1.val+l2.val;
ListNode node=new ListNode(val%10);//当前加数节点
if (sumHelper.sum!=null)
node.next=sumHelper.sum;
sumHelper.carry=val/10;
return sumHelper;//最后返回的是一个加数节点和进位
}
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
if (l1==null||l2==null) return l1==null?l2:l1;
int count1=0;
ListNode p1=l1;
while (p1!=null){
count1++;
p1= p1.next;
}
int count2=0;
ListNode p2=l1;
while (p2!=null){
count2++;
p2= p2.next;
}
if (count1<count2) return addTwoNumbers(l2,l2);
//保证l1最长
int diff=count1-count2;
ListNode head2=new ListNode(0);
ListNode p=head2;
for (int i=1;i<=diff;i++){
ListNode node=new ListNode(0);
p.next=node;
p=node;
}
if (diff!=0){
p.next=l2;
l2=head2;
}
PartialSum res=addHelper(l1,l2);
if (res.carry==0)
return res.sum;
else {
ListNode node=new ListNode(res.carry);//当前加数节点
if (res.sum!=null)
node.next=res.sum;
return node;
}
}
}
_15_回文链表
编写一个函数,检查输入的链表是否是回文的。
示例 1:
输入: 1->2
输出: false
示例 2:
输入: 1->2->2->1
输出: true
进阶:
你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?
package LeetCode._面试经典.笔记01;
import org.junit.Test;
import java.util.ArrayDeque;
public class _15_回文链表 {
public class ListNode {
int val;
ListNode next;
ListNode(int x) { val = x; }
}
//方法1:先将链表反转(头插),然后比较前半部分 O(N) O(N)
public boolean isPalindrome01(ListNode head) {
if (head==null) return true;
ListNode node=getReverse(head);
return isReverse(head,node);
}
public ListNode getReverse(ListNode head){
ListNode p=head;
ListNode r=null;
while (p!=null){
ListNode node=new ListNode(p.val);
node.next=r;
r=node;
p=p.next;
}
//最后一个节点为头节点
return r;
}
public boolean isReverse(ListNode l1,ListNode l2){
ListNode p1=l1;
ListNode p2=l2;
while (p1!=null&&p2!=null){
if (p1.val!=p2.val) return false;
p1=p1.next;
p2=p2.next;
}
return true;
}
//---------------------------------------------------------------
//方法2:迭代法,利用栈+快慢指针 O(N) O(N)
public boolean isPalindrome02(ListNode head) {
if (head==null) return true;
ListNode slow=head;
ListNode fast=head;
ArrayDeque<Integer> deque = new ArrayDeque<>();
while (fast!=null&&fast.next!=null){
deque.addFirst(slow.val);
slow=slow.next;
fast=fast.next.next;
}
//因为有奇数个节点,所以跳过中间节点
if (fast!=null) slow=slow.next;
while (slow!=null){
if (deque.removeFirst()!=slow.val) return false;
slow=slow.next;
}
return true;
}
//---------------------------------------------------------------
//方法3:递归,使用一个封装类
class Result{
ListNode node;
boolean result;
public Result(ListNode node, boolean result) {
this.node = node;
this.result = result;
}
}
public boolean isPalindrome(ListNode head){
int len=0;
ListNode p=head;
while (p!=null){
len++;
p=p.next;
}
Result reverse = isReverse(head, len);
return reverse.result;
}
//封装类的意思是返回 内序列的比对结果以及下次比较的内序列的最后1个节点
Result isReverse(ListNode head,int len){
if (head==null||len<=0){//偶数个节点
return new Result(head,true);
}
else if (len==1){//奇数个节点
return new Result(head.next,true);
}
Result res=isReverse(head.next,len-2);
if (!res.result||res.node==null) return res;//向上传递失败信息
res.result=(head.val==res.node.val);
res.node=res.node.next;
return res;
}
@Test
public void test(){
ListNode n1=new ListNode(1);
ListNode n2=new ListNode(1);
ListNode n3=new ListNode(2);
ListNode n4=new ListNode(1);
n1.next=n2;
n2.next=n3;
n3.next=n4;
boolean b = isPalindrome(n1);
System.out.println(b);
}
}
_16_链表相交
给定两个(单向)链表,判定它们是否相交并返回交点。请注意相交的定义基于节点的引用,而不是基于节点的值。换句话说,如果一个链表的第k个节点与另一个链表的第j个节点是同一节点(引用完全相同),则这两个链表相交。
示例 1:
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Reference of the node with value = 8
输入解释:相交节点的值为 8 (注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
示例 2:
输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Reference of the node with value = 2
输入解释:相交节点的值为 2 (注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4]。在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。
示例 3:
输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
输入解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
解释:这两个链表不相交,因此返回 null。
注意:
如果两个链表没有交点,返回 null 。
在返回结果后,两个链表仍须保持原有的结构。
可假定整个链表结构中没有循环。
程序尽量满足 O(n) 时间复杂度,且仅用 O(1) 内存。
package LeetCode._面试经典.笔记01;
import org.junit.Test;
public class _16_链表相交 {
public class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x;
next = null;
}
}
/*
快慢指针:
1.长度差,若俩链尾结点不同,直接返回false
2. 1 2 3 4
1 2
长的先多走2步
* */
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if (headA==null||headB==null) return null;
int lenA=0;
int lenB=0;
ListNode A=headA;
ListNode B=headB;
while (A.next!=null){
lenA++;
A=A.next;
}
//A现在是尾结点
lenA+=1;
while (B.next!=null){
lenB++;
B=B.next;
}
lenB+=1;
if (A!=B) return null;
//第1条链比较长
if (lenA<lenB) return getIntersectionNode(headB,headA);//别漏了return
A=headA;
for (int i=1;i<=lenA-lenB;i++) A=A.next;
B=headB;
while (A!=null&&B!=null){
if (A==B) return A;
A=A.next;
B=B.next;
}
return null;
}
@Test
public void test(){
ListNode n1=new ListNode(4);
ListNode n2=new ListNode(1);
ListNode n3=new ListNode(8);
ListNode n4=new ListNode(4);
ListNode n5=new ListNode(5);
ListNode n21=new ListNode(5);
ListNode n22=new ListNode(0);
ListNode n23=new ListNode(1);
n1.next=n2;
n2.next=n3;
n3.next=n4;
n4.next=n5;
n21.next=n22;
n22.next=n23;
n23.next=n3;
ListNode res = getIntersectionNode(n1, n21);
System.out.println(res.val);
}
}
_17_环路检测
给定一个链表,如果它是有环链表,实现一个算法返回环路的开头节点。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
示例 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
解释:链表中没有环。
package LeetCode._面试经典.笔记01;
public class _17_环路检测 {
class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x;
next = null;
}
}
/*
两倍指针+数学规律;
先找到碰撞点(判断有无环)+同步走;
推导:2k-k=l = k
k-x
l-(k-x)=k-(k-x)=x
* */
public ListNode detectCycle(ListNode head) {
if (head==null) return null;
ListNode slow=head;
ListNode fast=head;
while (fast!=null&&fast.next!=null){
slow=slow.next;
fast=fast.next.next;
if (slow==fast) break;
}
//slow.next==null是为了防止当个结点不进上面while循环
if (fast==null||fast.next==null) return null;//无环
slow=head;
while (slow!=fast){
slow=slow.next;
fast=fast.next;
}
return fast;
}
}
_18_用1个数组实现3栈
三合一。描述如何只用一个数组来实现三个栈。
你应该实现push(stackNum, value)、pop(stackNum)、isEmpty(stackNum)、peek(stackNum)方法。stackNum表示栈下标,value表示压入的值。
构造函数会传入一个stackSize参数,代表每个栈的大小。
示例1:
输入:
[“TripleInOne”, “push”, “push”, “pop”, “pop”, “pop”, “isEmpty”]
[[1], [0, 1], [0, 2], [0], [0], [0], [0]]
输出:
[null, null, null, 1, -1, -1, true]
说明:当栈为空时pop, peek
返回-1,当栈满时push
不压入元素。
示例2:
输入:
[“TripleInOne”, “push”, “push”, “push”, “pop”, “pop”, “pop”, “peek”]
[[2], [0, 1], [0, 2], [0, 3], [0], [0], [0], [0]]
输出:
[null, null, null, null, 2, 1, -1, -1]
package LeetCode._面试经典.笔记01;
public class _18_用1个数组实现3栈 {
private final int numOfStack=3;//有几个栈
private int capacityOfStack;//每个栈的容量
private int[] values;//分配的总数组
private int[] size;//每个栈的大小
public _18_用1个数组实现3栈(int stackSize) {
capacityOfStack=stackSize;
values=new int[numOfStack*capacityOfStack];
size=new int[numOfStack];
}
/*
首先需要实现2个额外方法
1.取得栈顶元素的下标
2.判断栈已满(毕竟数组长度固定)
* */
public boolean isFull(int stackNum){
return size[stackNum]==capacityOfStack;
}
public int indexOfTop(int stackNum){
//基址+偏移量
return stackNum*capacityOfStack+size[stackNum]-1;
}
public void push(int stackNum, int value) {
if (!isFull(stackNum)){
values[indexOfTop(stackNum)+1]=value;
size[stackNum]++;
}
}
public int pop(int stackNum) {
if (!isEmpty(stackNum)){
int top=values[indexOfTop(stackNum)];
size[stackNum]--;
return top;
}
return -1;
}
public int peek(int stackNum) {
if (!isEmpty(stackNum)){
return values[indexOfTop(stackNum)];
}
return -1;
}
public boolean isEmpty(int stackNum) {
return size[stackNum]==0;
}
}
_19_栈的最小值
请设计一个栈,除了常规栈支持的pop与push函数以外,还支持min函数,该函数返回栈元素中的最小值。执行push、pop和min操作的时间复杂度必须为O(1)。
示例:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin(); --> 返回 -3.
minStack.pop();
minStack.top(); --> 返回 0.
minStack.getMin(); --> 返回 -2.
package LeetCode._面试经典.笔记01;
import java.util.ArrayDeque;
import java.util.Stack;
public class _19_栈的最小值 {
//使用链表来实现1个栈
public class Node{
int data;
Node next;
Node(){}
Node(int data){this.data=data;}
Node(int data,Node next){this.data=data;this.next=next;}
}
public class myStack{
Node Top=null;
//实现4个方法:isEmpty,peek,push,pop
public boolean isEmpty(){
return Top==null;
}
public int peek(){
if (Top==null) return -1;
return Top.data;
}
public void push(int data){//栈顶元素为链表头元素,头插法
Node node = new Node(data);
node.next=Top;
Top=node;
}
public int pop(){
if (Top==null) return -1;
int value=Top.data;
Top=Top.next;
return value;
}
}
myStack elem;
myStack min;
public _19_栈的最小值() {
elem=new myStack();
min=new myStack();
}
public void push(int x) {
elem.push(x);
//min.isEmpty()犯错点,没判断空
if (min.isEmpty()||x<=min.peek()) min.push(x);
}
public void pop() {
// new Stack<Integer>().peek();犯错点,如果调用库函数的话,这里要用equals或者先强转
if (elem.peek()==min.peek())
min.pop();
elem.pop();
}
public int top() {
return elem.peek();
}
public int getMin() {
return min.peek();
}
}
_20_堆盘子
堆盘子。设想有一堆盘子,堆太高可能会倒下来。因此,在现实生活中,盘子堆到一定高度时,我们就会另外堆一堆盘子。请实现数据结构SetOfStacks,模拟这种行为。SetOfStacks应该由多个栈组成,并且在前一个栈填满时新建一个栈。此外,SetOfStacks.push()和SetOfStacks.pop()应该与普通栈的操作方法相同(也就是说,pop()返回的值,应该跟只有一个栈时的情况一样)。 进阶:实现一个popAt(int index)方法,根据指定的子栈,执行pop操作。
当某个栈为空时,应当删除该栈。当栈中没有元素或不存在该栈时,pop,popAt 应返回 -1.
示例1:
输入:
[“StackOfPlates”, “push”, “push”, “popAt”, “pop”, “pop”]
[[1], [1], [2], [1], [], []]
输出:
[null, null, null, 2, 1, -1]
示例2:
输入:
[“StackOfPlates”, “push”, “push”, “push”, “popAt”, “popAt”, “popAt”]
[[2], [1], [2], [3], [0], [0], [0]]
输出:
[null, null, null, null, 2, 1, 3]
package LeetCode._面试经典.笔记01;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
public class _20_堆盘子 {
int maxCaps;//每个栈的最大容量
List<Stack<Integer>> list;//用链表存储栈,增删更快
public _20_堆盘子(int cap) {
this.maxCaps=cap;
this.list=new ArrayList<>();
}
public void push(int val) {
if (maxCaps<=0) return;
if (list.isEmpty()){//第一个盘子堆
Stack<Integer> arr = new Stack<>();
arr.push(val);
list.add(arr);
}
else {
Stack<Integer> oldArr = list.get(list.size() - 1);
int size=oldArr.size();
if (size==maxCaps){//需要新建1个栈
Stack<Integer> arr = new Stack<>();
arr.push(val);
list.add(arr);
}else {
oldArr.add(val);
}
}
}
public int pop() {
if (list.size()==0) return -1;
Stack<Integer> oldArr = list.get(list.size() - 1);
int re = oldArr.peek();
oldArr.pop();
if (oldArr.size()==0) list.remove(list.size() - 1);
return re;
}
public int popAt(int index) {
if (list.size()==0||index<0||index>list.size()-1) return -1;
Stack<Integer> oldArr = list.get(index);
int re = oldArr.peek();
oldArr.pop();
if (oldArr.size()==0) list.remove(index);
return re;
}
}