初级4-2-18:00
给定一个有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)
比如这组数据,找4
方法一,如上图,从右上角开始找,如果大于4,就向左走(因为下面的肯定也大于4了),直到一个小于4的(等于不就找到了嘛),开始向下走,如果又遇到大于4的,继续向左走,直到越界没找到的话,返回false.
方法二:或从左下角开始,当前数比k小向右,当前数比k大向上
public static boolean isContains(int[][] matrix, int K) {
int row = 0;
int col = matrix[0].length - 1;
while (row < matrix.length && col > -1) {
if (matrix[row][col] == K) {
return true;
} else if (matrix[row][col] > K) {
col--;
} else {
row++;
}
}
return false;
}
\
打印两个有序链表的公共部分 给定两个有序链表的头指针head1和head2,打印两个链表的公共部分
有点像外排,理解mergeSort
public static void printCommonPart(Node head1, Node head2){
Node cur1 = head1;
Node cur2 = head2;
while (cur1 != null && cur2 != null){
if (cur1.value < cur2.value){
cur1 = cur1.next;
}else if(cur1.value > cur2.value){
cur2 = cur2.next;
}else {
System.out.print(cur1.value+" ");
//注意在相等时,两个同时向前走
cur1 = cur1.next;
cur2 = cur2.next;
}
}
System.out.println();
}
tips:自己写的时候,在while循环前,可以先定义类似cur等标记变量,便于自己理解。先保证写的出来,精简代码时后面的事。
判断一个链表是否回文
注意,链表的题目,面试中,可尽量考虑空间复杂度(链表一般难点都在这),笔试的话,可尽快做完,尽量不需考虑空间
方法一:额外空间复杂度O(n) 将一个个数压栈,然后一个个出栈同原数一个个比较,不一样就不是回文。
方法二:额外空复,O(n/2) , 使用快慢指针,走到中间,放后一半到栈中然后比较,省一半空间。
方法三:不使用额外空间复杂度(O(1))。 快慢指针,快的走两步,慢的走一步。快的走完,慢指针的位置即在中点。然后反转后半段链表,重头到尾开始比较,指针指到null后停(比如下图中点3的下一个就是null)。
package com.godzuo.java;
import java.util.Stack;
/**
* @author quanquan
* @create 2020-04-22-17:02
*/
public class IsPalindromeList {
public static class Node{
private int value;
private Node next;
public Node(int value){
this.value = value;
}
}
// 需要O(N)额外空间复杂度
public static boolean isPalindrome1(Node head) {
Stack<Node> stack = new Stack<>();
Node cur = head;
while (cur != null) {
stack.push(cur);
cur = cur.next;
}
while (head != null) {
if (head.value != stack.pop().value) {
return false;
}
head = head.next;
}
return true;
}
// n/2 额外空复
public static boolean isPalindrome2(Node head) {
}
//需要O(1)额外空间复杂度
public static boolean isPalindrome3(Node head){
if (head == null || head.next == null) {
return true;
}
Node mid = head;
Node end = head;
//这里注意,链表快慢指针的循环条件,用快的那个指针来判定,否则可能空指针
while (end.next != null && end.next.next != null) { // find mid node
mid = mid.next;
end = end.next.next;
}
Node head2 = mid.next; // head2 -> right part first node
mid.next = null; // 中点节点的next置为空,将原链表分为两段
//开始反转后半段链表head2
//经典链表反转
Node pre = null;
Node cur = head2;
Node next = null;
while (cur != null) { // right part convert
next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
//将后半段链表反转后的头,重新赋值给head2
head2 = pre;
boolean res = true;
Node compare1 = head;
Node compare2 = head2;
//链表长度奇数个时,中间夹的那个在如下写法中不会进行判断
//比如1-》2-》3 和 2《-1 的 3,就不会参与比较了
while (compare1 != null && compare2 != null) { // check palindrome
if (compare1.value != compare2.value) {
res = false;
break;
}
compare1 = compare1.next; // left to mid
compare2 = compare2.next; // right to mid
}
//==============恢复链表
pre = null;
cur = head2;
next = null;
while (cur != null) { // 恢复链表
next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
mid.next = pre;
return res;
}
public static void printLinkedList(Node node){
System.out.print("Linked List: ");
while(node != null){
System.out.print(node.value + " ");
node = node.next;
}
System.out.println();
}
public static void main(String[] args) {
Node head = null;
printLinkedList(head);
System.out.print(isPalindrome1(head) + " | ");
System.out.println(isPalindrome3(head) + " | ");
printLinkedList(head);
System.out.println("=========================");
head = new Node(1);
head.next = new Node(2);
head.next.next = new Node(3);
head.next.next.next = new Node(1);
printLinkedList(head);
System.out.print(isPalindrome1(head) + " | ");
System.out.println(isPalindrome3(head) + " | ");
printLinkedList(head);
System.out.println("=========================");
head = new Node(1);
head.next = new Node(2);
head.next.next = new Node(2);
head.next.next.next = new Node(1);
printLinkedList(head);
System.out.print(isPalindrome1(head) + " | ");
System.out.println(isPalindrome3(head) + " | ");
printLinkedList(head);
System.out.println("=========================");
head = new Node(1);
head.next = new Node(2);
head.next.next = new Node(3);
head.next.next.next = new Node(2);
head.next.next.next.next = new Node(1);
printLinkedList(head);
System.out.print(isPalindrome1(head) + " | ");
System.out.println(isPalindrome3(head) + " | ");
printLinkedList(head);
System.out.println("=========================");
}
}
将单向链表按某值划分成左边小、中间相等、右边大的形式
【题目】 给定一个单向链表的头节点head,节点的值类型是整型,再给定一个整数pivot。实现一个调整链表的函数,将链表调整为左部分都是值小于pivot的节点,中间部分都是值等于pivot的节点,右部分都是值大于 pivot的节点。除这个要求外,对调整后的节点顺序没有更多的要求。 例如:链表9->0->4->5->1,pivot=3。调整后链表可以是1->0->4->9->5,也可以是0->1->9->5->4。总之,满足左部分都是小于3的节点,中间部分都是等于3的节点(本例中这个部分为空),右部分都是大于3的节点即可。对某部分内部的节点顺序不做要求。
(链表形式的荷兰国旗问题,先将链表转为数组,排序完了,再转回去)
【进阶】 在原问题的要求之上再增加如下两个要求。在左、中、右三个部分的内部也做顺序要求,要求每部分里的节点从左到右的顺序与原链表中节点的先后次序一致。 例如:链表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)。
(此时需要排序稳定性,而荷兰国旗不具备稳定性,需要另外的思路)
package com.godzuo.java;
/**
* @author quanquan
* @create 2020-04-22-22:45
*/
public class SmallerEqualBigger {
public static class Node{
private int value;
private Node next;
public Node(int num){
this.value = num;
}
}
//方法一:新建一个nodeArr数组,将原数组中的节点根据Partition放入数组中,再用链表连接起来
public static Node listPartition1(Node head, int pivot){
if(head == null){
return head;
}
Node cur = head;
int i = 0;
while (cur != null){
i++;
cur = cur.next;
}
Node[] nodeArr = new Node[i];
i = 0;
cur = head;
for (i = 0;i != nodeArr.length; i++){
nodeArr[i] = cur;
cur = cur.next;
}
arrPartition(nodeArr,pivot);
for(i = 1;i<nodeArr.length;i++){
nodeArr[i-1].next = nodeArr[i];
}
nodeArr[i-1].next = null;
return nodeArr[0];
}
public static void arrPartition(Node[] nodeArr,int pivot){
int small = -1;
int big = nodeArr.length;
int index = 0;
while (index != big){
if(nodeArr[index].value < pivot){
swap(nodeArr,++small,index++);
}else if(nodeArr[index].value == pivot){
index++;
}else{
swap(nodeArr,--big,index);
}
}
}
public static void swap(Node[] nodeArr, int a, int b) {
Node tmp = nodeArr[a];
nodeArr[a] = nodeArr[b];
nodeArr[b] = tmp;
}
//方法二:时间复杂度达O(N),额外空间复杂度O(1),且具有稳定性
//定义出六个变量,小于等于大于的头尾,然后向这些区域发货,最后将这些区域重新连接起来
public static Node listPartition2(Node head,int pivot){
Node sH = null; // small head
Node sT = null; // small tail
Node eH = null; // equal head
Node eT = null; // equal tail
Node bH = null; // big head
Node bT = null; // big tail
Node next = null; // save next node
// every node distributed to three lists
while (head != null){
next = head.next;
head.next = null;
if(head.value < pivot){
if(sH == null){
sH = head;
sT = head;
}else {
sT.next = head;
sT = head;
}
} else if (head.value == pivot) {
if (eH == null) {
eH = head;
eT = head;
} else {
eT.next = head;
eT = head;
}
} else {
if (bH == null) {
bH = head;
bT = head;
} else {
bT.next = head;
bT = head;
}
}
head = next;
}
// small and equal reconnect
if(sT != null){
sT.next = eH;
eT = eT == null ? sT : eT;
}
// all reconnect
if (eT != null) {
eT.next = bH;
}
return sH != null ? sH : eH != null ? eH : bH;
}
public static void printLinkedList(Node node){
System.out.println("Linked list:");
while (node != null){
System.out.print(node.value + " ");
node = node.next;
}
System.out.println();
}
public static void main(String[] args) {
Node head1 = new Node(7);
head1.next = new Node(9);
head1.next.next = new Node(1);
head1.next.next.next = new Node(8);
head1.next.next.next.next = new Node(5);
head1.next.next.next.next.next = new Node(2);
head1.next.next.next.next.next.next = new Node(5);
printLinkedList(head1);
//head1 = listPartition1(head1, 4);
head1 = listPartition2(head1, 5);
printLinkedList(head1);
}
}
初级4-2-1:00:00
复制含有随机指针节点的链表
【题目】 一种特殊的链表节点类描述如下:
public class Node {
public int value;
public Node next;
public Node rand;
public Node(int data) { this.value = data; }
}
Node类中的value是节点值,next指针和正常单链表中next指针的意义一 样,都指向下一个节点,rand指针是Node类中新增的指针,这个指针可能指向链表中的任意一个节点,也可能指向null。 给定一个由Node节点类型组成的无环单链表的头节点head,请实现一个 函数完成这个链表中所有结构的复制,并返回复制的新链表的头节点。
进阶:不使用额外的数据结构,只用有限几个变量,且在时间复杂度为 O(N)内完成原问题要实现的函数。
package com.godzuo.java;
import javax.xml.bind.annotation.adapters.HexBinaryAdapter;
import java.util.HashMap;
/**
* @author quanquan
* @create 2020-04-23-14:18
*/
public class CopyListWithRandom {
public static class Node{
private int value;
private Node next;
private Node rand;
public Node(int num){
this.value = num;
}
}
//利用哈希表HashMap,额外空间复杂度O(N)
public static Node copyListWithRand1(Node head) {
HashMap<Node, Node> map = new HashMap<Node, Node>();
Node cur = head;
while (cur != null) {
map.put(cur, new Node(cur.value));
cur = cur.next;
}
cur = head;
while (cur != null) {
//通过哈希表来找到对应复制出的那个节点并赋值
map.get(cur).next = map.get(cur.next);
map.get(cur).rand = map.get(cur.rand);
cur = cur.next;
}
return map.get(head);
}
//不利用HashMap,额外空间复杂度O(1)
//借由本身链表的数据结构(下一个)来拿到对应复制出的节点并进行相应赋值
public static Node copyListWithRand2(Node head){
if (head == null){
return null;
}
Node cur = head;
Node next = null;
//copy node and link to every node
while (cur != null){
next = cur.next;
cur.next = new Node(cur.value);
cur.next.next = next;
cur = next;
}
cur = head;
Node curCopy = null;
// set copy node rand
while (cur != null){
next = cur.next.next;
curCopy = cur.next;
curCopy.rand = cur.rand != null ? cur.rand : null;
cur = next;
}
Node res = head.next;
return res;
}
public static void printRandLinkedList(Node head) {
Node cur = head;
System.out.print("order: ");
while (cur != null) {
System.out.print(cur.value + " ");
cur = cur.next;
}
System.out.println();
cur = head;
System.out.print("rand: ");
while (cur != null) {
System.out.print(cur.rand == null ? "- " : cur.rand.value + " ");
cur = cur.next;
}
System.out.println();
}
public static void main(String[] args) {
Node head = null;
Node res1 = null;
Node res2 = null;
printRandLinkedList(head);
res1 = copyListWithRand1(head);
printRandLinkedList(res1);
// res2 = copyListWithRand2(head);
// printRandLinkedList(res2);
// printRandLinkedList(head);
System.out.println("=========================");
head = new Node(1);
head.next = new Node(2);
head.next.next = new Node(3);
head.next.next.next = new Node(4);
head.next.next.next.next = new Node(5);
head.next.next.next.next.next = new Node(6);
head.rand = head.next.next.next.next.next; // 1 -> 6
head.next.rand = head.next.next.next.next.next; // 2 -> 6
head.next.next.rand = head.next.next.next.next; // 3 -> 5
head.next.next.next.rand = head.next.next; // 4 -> 3
head.next.next.next.next.rand = null; // 5 -> null
head.next.next.next.next.next.rand = head.next.next.next; // 6 -> 4
printRandLinkedList(head);
res1 = copyListWithRand1(head);
printRandLinkedList(res1);
res2 = copyListWithRand2(head);
printRandLinkedList(res2);
printRandLinkedList(head);
System.out.println("=========================");
}
}
两个单链表相交的一系列问题
【题目】 在本题中,单链表可能有环,也可能无环。给定两个单链表的头节点 head1和head2,这两个链表可能相交,也可能不相交。请实现一个函数, 如果两个链表相交,请返回相交的第一个节点;如果不相交,返回null 即可。
要求:如果链表1的长度为N,链表2的长度为M,时间复杂度请达到 O(N+M),额外空间复杂度请达到O(1)。
- 判断一个单向链表是否有环
-
- 循环链表,将每个节点放入hash表(可使用hashSet),放入前先判断是否已包含,不包含就放入,包含的话即入环的节点,也即判断出是否有环。直到循环完也没有就无环
- 不使用hash表:快慢指针,快指针每次走两步,慢指针每次走一步,如果有环,快慢指针会相遇(即指向同一个节点),在相遇的地方,快指针回到链表的头部,改为一次走一步,再次相遇的地方就为入环的点(是个数学证明问题,貌似使用归纳法可证明)。如果快指针提前碰到null时直接返回,即无环。
- 两个无环单链表是否相交
-
- tips:单链表相交,只能是Y形状,不可能X形状;
- 循环两个单链表,分别获得到各自的最后一个节点end,和自身的长度len。 如果两个end相等(此处指内存地址相同,即同一个node)则表示相交;
- 如何获得第一个相交的节点(最后一个节点未必是第一相交的节点):比如len1=100,len2=80,那么先让链表1走20步,然后链表2再和1一起走,第一个相等的即所要求的节点。
- 一个单链表有环,一个无环,该两个链表不可能相交。(还是因为都是单链表)
- 两个有环链表相交,如何找到第一个入环节点
-
- 两个单链表都有环时,只有如下三种拓扑关系(需要自己画一画理解)
\
package com.godzuo.java;
/**
* @author quanquan
* @create 2020-04-23-21:36
*/
public class FindFirstIntersectNode {
public static class Node{
private int value;
private Node next;
public Node(int data){
this.value = data;
}
}
//额外空间复杂度O(1),如果使用HashSet额外空间复杂度O(N),但会简单一点
public static Node getIntersectNode(Node head1,Node head2){
if (head1 == null || head2 == null){
return null;
}
Node loop1 = getLoopNode(head1); //得到链表1的第一个入环节点
Node loop2 = getLoopNode(head2); //得到链表2的第一个入环节点
if (loop1 == null && loop2 == null){
return noLoop(head1,head2); //两个无环链表的相交问题
}
if (loop1 != null && loop2 != null){
return bothLoop(head1,loop1,head2,loop2); //两个有环链表的相交问题
}
return null;
}
public static Node getLoopNode(Node head){
if (head == null || head.next == null || head.next.next == null){
return null;
}
Node n1 = head.next; // n1 -> slow 快指针一次走两步
Node n2 = head.next.next; // n2 -> fast 慢指针一次走一步
while (n1 != n2){
if (n2.next == null || n2.next.next == null){
return null;
}
n2 = n2.next.next;
n1 = n1.next;
}
n2 = head; // n2 -> walk again from head 相遇后快指针回到开头
while (n1 != n2){
n1 = n1.next;
n2 = n2.next;
}
return n1;
}
//两个无环链表的相交问题
public static Node noLoop(Node head1, Node head2){
if (head1 == null || head2 == null){
return null;
}
Node cur1 = head1;
Node cur2 = head2;
int n = 0; //两个链表长度的差值
//while的时候想清楚while的边界值,这个边界就是“转”到链表最后一个,即cur.next为空时
//而不是cur!=null
while (cur1.next != null){
n++;
cur1 = cur1.next;
}
while (cur2.next != null){
n--;
cur2 = cur2.next;
}
if(cur1 != cur2){ //最后一个节点不相等,不可能相交
return null;
}
cur1 = n > 0 ? head1 : head2;
cur2 = cur1 == head1 ? head2 :head1;
n = Math.abs(n);
while (n != 0){
cur1 = cur1.next;
n--;
}
while (cur1 != cur2){
cur1 = cur1.next;
cur2 = cur2.next;
}
return cur1;
}
//两个有环链表的相交问题
public static Node bothLoop(Node head1, Node loop1, Node head2, Node loop2) {
Node cur1 = null;
Node cur2 = null;
//如果相交节点在环之外(即上图的情况2),两个链表成环点 一致,
//与两个无环链表相交问题一致。 只不过结束边界就在loop处
if (loop1 == loop2) {
cur1 = head1;
cur2 = head2;
int n = 0;
while (cur1 != loop1) {
n++;
cur1 = cur1.next;
}
while (cur2 != loop2) {
n--;
cur2 = cur2.next;
}
//cur1指定为那个较长链表的头
cur1 = n > 0 ? head1 : head2;
//cur2指定为较短。。。
cur2 = cur1 == head1 ? head2 : head1;
n = Math.abs(n);
while (n != 0) {
n--;
cur1 = cur1.next;
}
while (cur1 != cur2) {
cur1 = cur1.next;
cur2 = cur2.next;
}
return cur1;
} else { //剩余情况1或情况3,判断链表的环上有没有链表2
cur1 = loop1.next;
while (cur1 != loop1) {
//一直转1,如果有相交,则为情况3,直接返回
//情况3其实就是一圈节点都相交
if (cur1 == loop2) {
return loop1;
}
cur1 = cur1.next;
}
return null; //否则情况1,不相交
}
}
public static void main(String[] args) {
// 1->2->3->4->5->6->7->null
Node head1 = new Node(1);
head1.next = new Node(2);
head1.next.next = new Node(3);
head1.next.next.next = new Node(4);
head1.next.next.next.next = new Node(5);
head1.next.next.next.next.next = new Node(6);
head1.next.next.next.next.next.next = new Node(7);
// 0->9->8->6->7->null
Node head2 = new Node(0);
head2.next = new Node(9);
head2.next.next = new Node(8);
head2.next.next.next = head1.next.next.next.next.next; // 8->6
System.out.println(getIntersectNode(head1, head2).value);
// 1->2->3->4->5->6->7->4...
head1 = new Node(1);
head1.next = new Node(2);
head1.next.next = new Node(3);
head1.next.next.next = new Node(4);
head1.next.next.next.next = new Node(5);
head1.next.next.next.next.next = new Node(6);
head1.next.next.next.next.next.next = new Node(7);
head1.next.next.next.next.next.next = head1.next.next.next; // 7->4
// 0->9->8->2...
head2 = new Node(0);
head2.next = new Node(9);
head2.next.next = new Node(8);
head2.next.next.next = head1.next; // 8->2
System.out.println(getIntersectNode(head1, head2).value);
// 0->9->8->6->4->5->6..
head2 = new Node(0);
head2.next = new Node(9);
head2.next.next = new Node(8);
head2.next.next.next = head1.next.next.next.next.next; // 8->4
System.out.println(getIntersectNode(head1, head2).value);
}
}