左神算法-基础04-链表

左神算法-基础04-链表

哈希表的简单介绍

1)哈希表在使用层面上可以理解为一种集合结构

2)如果只有key,没有伴随数据value,可以使用HashSet结构(C++中叫UnOrderedSet)

3)如果既有key,又有伴随数据value,可以使用HashMap结构(C++中叫UnOrderedMap)

4)有无伴随数据,是HashMap和HashSet唯一的区别底层的实际结构是一回事

5)使用哈希表增(put)、删(remove)、改(put)和查(get)的操作,可以认为时间复杂度为O(1),但是常数时间比较大

6)放入哈希表的东西,如果是基础类型,内部按值传递内存占用就是这个东西的大小

7)放入哈希表的东西,如果不是基础类型,内部按引用传递内存占用是这个东西内存地址的大小

有序表的简单介绍

1)有序表在使用层面上可以理解为一种集合结构

2)如果只有key,没有伴随数据value,可以使用TreeSet结构(C++中叫OrderedSet)

3)如果既有key,又有伴随数据value,可以使用TreeMap结构(C++中叫OrderedMap)

4)有无伴随数据,是TreeSet和TreeMap唯一的区别,底层的实际结构是一回事

5)有序表和哈希表的区别是,有序表把key按照顺序组织起来而哈希表完全不组织

5)红黑树、AVL树、size-balance-tree和跳表等都属于有序表结构,只是底层具体实现不同

6)放入有序表的东西,如果是基础类型,内部按值传递,内存占用就是这个东西的大小

7)放入有序表的东西,如果不是基础类型必须提供比较器,内部按引用传递,内存占用是这个东西内存地址的大小

8)不管是什么底层具体实现,只要是有序表,都有以下固定的基本功能和固定的时间复杂度

有序表的固定操作

1)void put(K key, V value):将一个(key,value)记录加入到表中,或者将key的记录更新成value

2)V get(K key):根据给定的key,查询value并返回。

3)void remove(K key):移除key的记录

4)boolean containsKey(K key):询问是否有关于key的记录

5)K firstKey():返回所有键值的排序结果中最左(最小)的那个

6)K lastKey():返回所有键值的排序结果中最右(最大)的那个。

7)K floorKey(K key):如果表中存入过key,返回key;否则返回所有键值的排序结果中, key的前一个。 返回<=key 的最大值

8)K ceilingKey(K key):如果表中存入过key,返回key;否则返回所有键值的排序结果中,key的后一个。 返回>=key的最小值

以上所有操作时间复杂度都是O(logN),N为有序表含有的记录数

public static class Node {
   public int value;
   public Node next;

   public Node(int val) {
      value = val;
   }
}

public static class NodeComparator implements Comparator<Node> {

   @Override
   public int compare(Node o1, Node o2) {
      return o1.value - o2.value;
   }

}

public static void main(String[] args) {
   Node nodeA = null;
   Node nodeB = null;
   Node nodeC = null;

   // hashSet1的key是基础类型->int类型
   HashSet<Integer> hashSet1 = new HashSet<>();
   hashSet1.add(3);
   System.out.println(hashSet1.contains(3));
   hashSet1.remove(3);
   System.out.println(hashSet1.contains(3));
   System.out.println("========1=========");

   // hashSet2的key是非基础类型->Node类型
   nodeA = new Node(1);
   nodeB = new Node(1);
   HashSet<Node> hashSet2 = new HashSet<>();
   hashSet2.add(nodeA);
   System.out.println(hashSet2.contains(nodeA));
   System.out.println(hashSet2.contains(nodeB));
   hashSet2.remove(nodeA);
   System.out.println(hashSet2.contains(nodeA));
   System.out.println("========2=========");

   // hashMap1的key是基础类型->String类型
   HashMap<String, Integer> hashMap1 = new HashMap<>();
   String str1 = "key";
   String str2 = "key";
   hashMap1.put(str1, 1);
   System.out.println(hashMap1.containsKey(str1));
   System.out.println(hashMap1.containsKey(str2));
   System.out.println(hashMap1.get(str1));
   System.out.println(hashMap1.get(str2));

   hashMap1.put(str2, 2);
   System.out.println(hashMap1.containsKey(str1));
   System.out.println(hashMap1.containsKey(str2));
   System.out.println(hashMap1.get(str1));
   System.out.println(hashMap1.get(str2));

   hashMap1.remove(str1);
   System.out.println(hashMap1.containsKey(str1));
   System.out.println(hashMap1.containsKey(str2));
   System.out.println("========3=========");

   // hashMap2的key是非基础类型->Node类型
   nodeA = new Node(1);
   nodeB = new Node(1);
   HashMap<Node, String> hashMap2 = new HashMap<>();
   hashMap2.put(nodeA, "A节点");
   System.out.println(hashMap2.containsKey(nodeA));
   System.out.println(hashMap2.containsKey(nodeB));
   System.out.println(hashMap2.get(nodeA));
   System.out.println(hashMap2.get(nodeB));
   hashMap2.put(nodeB, "B节点");
   System.out.println(hashMap2.containsKey(nodeA));
   System.out.println(hashMap2.containsKey(nodeB));
   System.out.println(hashMap2.get(nodeA));
   System.out.println(hashMap2.get(nodeB));
   System.out.println("========4=========");

   // treeSet的key是非基础类型->Node类型
   nodeA = new Node(5);
   nodeB = new Node(3);
   nodeC = new Node(7);

   TreeSet<Node> treeSet = new TreeSet<>();
   // 以下的代码会报错,因为没有提供Node类型的比较器
   try {
      treeSet.add(nodeA);
      treeSet.add(nodeB);
      treeSet.add(nodeC);
   } catch (Exception e) {
      System.out.println("错误信息:" + e.getMessage());
   }

   treeSet = new TreeSet<>(new NodeComparator());
   // 以下的代码没问题,因为提供了Node类型的比较器
   try {
      treeSet.add(nodeA);
      treeSet.add(nodeB);
      treeSet.add(nodeC);
      System.out.println("这次节点都加入了");
   } catch (Exception e) {
      System.out.println(e.getMessage());
   }
   System.out.println("========5=========");

   // 展示有序表常用操作
   TreeMap<Integer, String> treeMap1 = new TreeMap<>();
   treeMap1.put(7, "我是7");
   treeMap1.put(5, "我是5");
   treeMap1.put(4, "我是4");
   treeMap1.put(3, "我是3");
   treeMap1.put(9, "我是9");
   treeMap1.put(2, "我是2");
   System.out.println(treeMap1.containsKey(5));
   System.out.println(treeMap1.get(5));
   //依据比较器进行排序
   System.out.println(treeMap1.firstKey() + ", 我最小");
   System.out.println(treeMap1.lastKey() + ", 我最大");
   //floorKey ceilingKey
   System.out.println(treeMap1.floorKey(8) + ", 在表中所有<=8的数中,我离8最近");
   System.out.println(treeMap1.ceilingKey(8) + ", 在表中所有>=8的数中,我离8最近");
   System.out.println(treeMap1.floorKey(7) + ", 在表中所有<=7的数中,我离7最近");
   System.out.println(treeMap1.ceilingKey(7) + ", 在表中所有>=7的数中,我离7最近");
   treeMap1.remove(5);
   System.out.println(treeMap1.get(5) + ", 删了就没有了哦");
   System.out.println("========6=========");

}

链表

单链表的节点结构

class Node<V>{ 
	V value; 
	Node next; 
}

由以上结构的节点依次连接起来所形成的链叫单链表结构。

双链表的节点结构

class Node<V>{ 
	V value; 
	Node next; 
	Node last; 
}

由以上结构的节点依次连接起来所形成的链叫双链表结构。

单链表和双链表结构只需要给定一个头部节点head,就可以找到剩下的所有的节点。

反转单向和双向链表

【题目】 分别实现反转单向链表和反转双向链表的函数

【要求】 如果链表长度为N,时间复杂度要求为O(N),额外空间复杂度要求为

O(1)

    //单向链表节点
   public static class Node {
      public int value;
      public Node next;

      public Node(int data) {
         this.value = data;
      }
   }

   public static Node myReverseList(Node head) {
      Node p = null;
      while(head != null) {
         Node t = head.next;
         head.next = p;
         p = head;
         head = t;
      }
      return p;
   }
	//双向链表节点
   public static class DoubleNode {
      public int value;
      public DoubleNode last;
      public DoubleNode next;

      public DoubleNode(int data) {
         this.value = data;
      }
   }

   public static DoubleNode myReverseList(DoubleNode head) {
      DoubleNode p = null;
      DoubleNode newHead = null;
      
//    while(head != null) {
//       DoubleNode tmp = head.next;
//       head.next = p;
//       head.last = tmp;
//       p = head;
//       head = tmp;
//    }
//    
//    return p;

      while(head != null) {
         DoubleNode t = head.next;
         head.next = p;
         p = head;
         head = t;
      }
      newHead = p;
      while(p != null) {
         DoubleNode t = p.last;
         p.last = head;
         head = p;
         p = t;
      }

      return newHead;
   }

打印两个有序链表的公共部分

【题目】 给定两个有序链表的头指针head1和head2,打印两个链表的公共部分。

【要求】 如果两个链表的长度之和为N,时间复杂度要求为O(N),额外空间复

杂度要求为O(1)

public static class Node {
   public int value;
   public Node next;
   public Node(int data) {
      this.value = data;
   }
}

public static void myPrintCommonPart(Node head1, Node head2) {
   //有序链表,遍历,谁小谁往后走,
   //相等输出,然后一起走
   System.out.print("Common Part: ");
   for (; head1 != null && head2 != null ; ) {
      if (head1.value > head2.value) {
         head2 = head2.next;
      }else if (head1.value < head2.value){
         head1 = head1.next;
      }else {
         System.out.print(head1.value+"\t");
         head1 = head1.next;
         head2 = head2.next;
      }
   }
   System.out.println();
}

面试时链表解题的方法论

1)对于笔试,不用太在乎空间复杂度,一切为了时间复杂度

2)对于面试,时间复杂度依然放在第一位,但是一定要找到空间最省的方法

重要技巧:

1)额外数据结构记录(哈希表等)

2)快慢指针

判断一个链表是否为回文结构

【题目】给定一个单链表的头节点head,请判断该链表是否为回文结构。

【例子】1->2->1,返回true; 1->2->2->1,返回true;15->6->15,返回true; 1->2->3,返回false。

【例子】如果链表长度为N,时间复杂度达到O(N),额外空间复杂度达到O(1)。

public static boolean myIsPalindrome1(Node head) {
   //存到栈中,再读出来
   Stack<Node> nodes = new Stack<Node>();
   Node p = head;
   while(p != null) {
      nodes.push(p);
      p = p.next;
   }
   while(head.value == nodes.pop().value){
      if (head.next == null)
         return true;
      head = head.next;
   }
   return false;
}
// need n extra space
public static boolean isPalindrome1(Node head) {
   Stack<Node> stack = new Stack<Node>();
   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;
}

public static boolean myIsPalindrome2(Node head) {
   if (head == null || head.next == null) {
      return true;
   }
   //把一半存到栈中,再遍历后一半与栈.pop 作比较
   Stack<Node> nodes = new Stack<Node>();
   Node p1 = head;
   Node p2 = head;
   while(p2.next != null && p2.next.next != null) {
      p1 = p1.next;
      p2 = p2.next.next;
   }

   while(head != p1.next){
      nodes.push(head);
      head = head.next;
   }
   if (p2.next != null)   //当p2不是尾节点时,让p1后移一下,也就是总结点数是偶数的情况
      p1 = p1.next;
   //否则就不需要后移
   while(p1 != null) {
      if (p1.value != nodes.pop().value)
         return false;
      p1 = p1.next;
   }
   return true;
}
// need n/2 extra space
public static boolean isPalindrome2(Node head) {
   if (head == null || head.next == null) {
      return true;
   }
   Node right = head.next;
   Node cur = head;
   while (cur.next != null && cur.next.next != null) {
      right = right.next;
      cur = cur.next.next;
   }
   Stack<Node> stack = new Stack<Node>();
   //把后一半存到栈结构中
   while (right != null) {
      stack.push(right);
      right = right.next;
   }
   //用栈是否空作为条件,不需要分情况
   while (!stack.isEmpty()) {
      if (head.value != stack.pop().value) {
         return false;
      }
      head = head.next;
   }
   return true;
}

public static boolean myIsPalindrome3(Node head) {
   if (head == null || head.next == null) {
      return true;
   }
   Node p1 = head;
   Node p2 = head;
   while(p2.next != null && p2.next.next != null) {
      p1 = p1.next;
      p2 = p2.next.next;
   }
   //p2到了为尾,p1到了中间
   p2 = p1.next;
   Node p3 = null;
   while(p2 != null) {
      Node t = null;
      t = p2.next;
      p2.next = p3;
      p3 = p2;
      p2 = t;
   }
   p2 = p3;
   //p3 是后一段逆序的头节点
   boolean isPalindrome = true;
   while(p3!=null) {
      if (p3.value != head.value) {
         isPalindrome = false;
         break;
      }
      p3 = p3.next;
      head = head.next;
   }
   //要还原链表
   p3 = null;
   while(p2 != null) {
      Node t = null;
      t = p2.next;
      p2.next = p3;
      p3 = p2;
      p2 = t;
   }
   //p3 后一段的头
   p1.next = p3;
   return isPalindrome;
}

// need O(1) extra space
public static boolean isPalindrome3(Node head) {
   if (head == null || head.next == null) {
      return true;
   }
   Node n1 = head;
   Node n2 = head;
   while (n2.next != null && n2.next.next != null) { // find mid node
      n1 = n1.next; // n1 -> mid
      n2 = n2.next.next; // n2 -> end
   }
   n2 = n1.next; // n2 -> right part first node
   n1.next = null; // mid.next -> null
   Node n3 = null;
   while (n2 != null) { // right part convert
      n3 = n2.next; // n3 -> save next node
      n2.next = n1; // next of right node convert
      n1 = n2; // n1 move
      n2 = n3; // n2 move
   }
   n3 = n1; // n3 -> save last node
   n2 = head;// n2 -> left first node
   boolean res = true;
   while (n1 != null && n2 != null) { // check palindrome
      if (n1.value != n2.value) {
         res = false;
         break;
      }
      n1 = n1.next; // left to mid
      n2 = n2.next; // right to mid
   }
   n1 = n3.next;
   n3.next = null;
   while (n1 != null) { // recover list
      n2 = n1.next;
      n1.next = n3;
      n3 = n1;
      n1 = n2;
   }
   return res;
}

将单向链表按某值划分成左边小、中间相等、右边大的形式

【题目】给定一个单链表的头节点head,节点的值类型是整型,再给定一个整数pivot。

实现一个调整链表的函数,将链表调整为左部分都是值小于pivot的节点,中间部分都是值等于pivot的节点,右部分都是值大于pivot的节点。

【进阶】在实现原问题功能的基础上增加如下的要求

【要求】调整后所有小于pivot的节点之间的相对顺序和调整前一样

【要求】调整后所有等于pivot的节点之间的相对顺序和调整前一样

【要求】调整后所有大于pivot的节点之间的相对顺序和调整前一样

【要求】时间复杂度请达到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;
}

复制含有随机指针节点的链表

【题目】一种特殊的单链表节点类描述如下

class Node { 
	int value; 
	Node next; 
	Node rand; 
	Node(int val) { 
		value = val; 
	} 
}

rand指针是单链表节点结构中新增的指针,rand可能指向链表中的任意一个节点,也可能指向null。

给定一个由Node节点类型组成的无环单链表的头节点 head,请实现一个函数完成这个链表的复制,并返回复制的新链表的头节点。

【要求】时间复杂度O(N),额外空间复杂度O(1)

	//利用hashmap结构,将key 的对应关系复制到 value上面,返回get(head)就ok
	public static Node myCopyListWithRand1(Node head) {
		HashMap<Node, Node> map = new HashMap<Node, Node>();
		Node p = head;
		while (p != null){
			map.put(p,new Node(p.value));
			p = p.next;
		}
		p = head;
		while (p != null) {
			map.get(p).next = map.get(p.next);
			map.get(p).rand = map.get(p.rand);
			p = p.next;
		}
		return map.get(head);
	}
	//复制的节点跟在原链表节点的后面,串到一个链表上,将rand的关系对应到复制的节点上
	//最后将链表分离开来
	public static Node myCopyListWithRand2(Node head) {
		if (head == null)
			return null;
		Node p = head;
		Node t = null;
		while (p != null) {
			t = p.next;
			p.next = new Node(p.value);
			p.next.next = t;
			p = t;
		}
		p = head;
		t = p.next;
		while (t != null) {
			t.rand = p.rand == null ? null : p.rand.next;
			if (t.next == null)
				break;
			p = p.next.next;
			t = t.next.next;
		}
		p = head;
		Node newHead = head.next;
		while (p.next != null ) {
			t = p.next;
			p.next = t.next;
			p = t;
		}
		return newHead;
	}

两个单链表相交的一系列问题

【题目】给定两个可能有环也可能无环的单链表,头节点head1和head2。

请实现一个函数,如果两个链表相交,请返回相交的 第一个节点。如果不相交,返回null

【要求】如果两个链表长度之和为N,时间复杂度请达到O(N),额外空间复杂度请达到O(1)。

//分为都有环和都无环两种情况
public static Node myGetIntersectNode(Node head1, Node head2) {
   if (head1 == null || head2 == null)
      return null;
   Node loop1 = myGetLoopNode(head1);
   Node loop2 = myGetLoopNode(head2);

   if (loop1 == null && loop2 == null) {
      return myNoLoop(head1, head2);
   }else if (loop1 != null && loop2 !=null){
      return myBothLoop(head1, loop1, head2, loop2);
   }
   return null;

}
//找入环节点
public static Node myGetLoopNode(Node head) {
   if (head == null || head.next == null || head.next.next == null)
      return null;
   Node slow = head;
   Node fast = head.next;
   while (fast != slow && fast != null) {
      slow = slow.next;
      fast = fast.next == null ? null : fast.next.next;
   }
   if (fast == null)
      return null;
   fast = null;
   while (fast!=slow){
      fast = fast == null ? head : fast.next;
      slow = slow.next;
   }
   return fast;
}
//都无环的情况
public static Node myNoLoop(Node head1, Node head2) {
   Node p1 = head1;
   Node p2 = head2;
   int s1 = 0;
   int s2 = 0;
   int ds = 0;
   while(p1.next != null) {
      p1 = p1.next;
      s1++;
   }
   while(p2.next != null) {
      p2 = p2.next;
      s2++;
   }
   if (p1 != p2)  // 尾节点不一样,直接返回null
      return null;
   ds = Math.abs(s1 - s2);
   p1 = head1;
   p2 = head2;
   if (s1 >= s2){
      while(ds > 0){
         p1 = p1.next;
         ds--;
      }
   }else {
      while(ds > 0){
         p2 = p2.next;
         ds--;
      }
   }
   while (p1 != p2){
      p1 = p1.next;
      p2 = p2.next;
   }
   return p1;
}
//都有环的情况
public static Node myBothLoop(Node head1, Node loop1, Node head2, Node loop2) {
   if (loop1 == loop2) {
      Node p1 = head1;
      Node p2 = head2;
      int s1 = 0;
      int s2 = 0;
      int ds = 0;
      while (p1 != loop1) {
         p1 = p1.next;
         s1++;
      }
      while (p2 != loop1) {
         p2 = p2.next;
         s2++;
      }
      ds = Math.abs(s1 - s2);
      p1 = head1;
      p2 = head2;
      if (s1 >= s2) {
         while (ds > 0) {
            p1 = p1.next;
            ds--;
         }
      } else {
         while (ds > 0) {
            p2 = p2.next;
            ds--;
         }
      }
      while (p1 != p2) {
         p1 = p1.next;
         p2 = p2.next;
      }
      return p1;
   }else {
      Node p1 = loop1;
      while (p1 != loop2){
         p1 = p1.next;
         if (p1 == loop1)
            return null;
      }
      return loop1;
   }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值