提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
链表在算法题比数组少很多,而在回溯贪心动规等高级算法中很少见到链表的影子。我们这里就集中研究一些出现频率特别高的算法题!
1、两个链表的第一个公共子节点问题!
输出两个链表,找出它们的第一个公告子节点。
首先在这边,我需要注意的是,链表要求是环环相扣的,核心是一个节点只能有一个后继,但是不代表一个节点只能被一个指向。
在没有思路的情况下,我们该如何解题呢?
以下为两种方式解析:
hashmap的方法:
put(K key,V value) 增添数据
remove(Object Key) 根据键删除键值对元素
void clear() 移除所有的键值对元素
boolean containsKey(Object key) 判断集合是否包含指定的键
boolean containsValue(Object value) 判断集合是否包含指定的值
boolean isEmpty() 判断集合是否为空
V get(Object key)() 根据键获取值
Set keySet() 获取所有键集合
代码如下(示例):
public static void main(String[] args) {
//创建并获取初始化链表
ListNode[] heads = initLinkedList();
//la 为 1 2 3 4 5
//lb 为 11 22 4 5
ListNode la = heads[0];
ListNode lb = heads[1];
ListNode node = new ListNode(0);
node = hhztFindFirstCommonNodeByMap(la, lb);
System.out.println("map解析公共节点为:"+node.val);
node = hhztFindFirstCommonNodeBySet(la, lb);
System.out.println("set解析公共节点为:"+node.val);
node = hhztFindFirstCommonNodeByStack(la, lb);
System.out.println("栈解析公共节点为:"+node.val);
}
/**
* hashMap方法解析 两个链表,找出它们的第一个公告子节点
* @param listNode1
* @param listNode2
* @return
*/
public static ListNode hhztFindFirstCommonNodeByMap(ListNode listNode1,ListNode listNode2){
//判空
if (listNode1==null||listNode2==null){
return null;
}
Map<ListNode,String> nodeHashMap= new HashMap<ListNode,String>();
// Map nodeHashMap2= new HashMap<>();
//错误写法,containsKey()用法的理解错误
/*while(listNode1 != null){
nodeHashMap1.put(listNode1,null);
listNode1 = listNode1.next;
}
while(listNode2 != null){
nodeHashMap2.put(listNode2,null);
listNode2 = listNode2.next;
}
boolean b = nodeHashMap1.containsKey(nodeHashMap2);*/
ListNode node1 = listNode1;
ListNode node2 = listNode2;
//怎么循环呢?将链表的数据存入map中,难点在与 每次存入节点后,都要指向下个节点,
//每次存入节点,比如节点1-->2-->3,第一个map存入1-->2--3,第二个map存入2--3,第三个map存入3
while(node1 != null){
nodeHashMap.put(node1,null);
node1 = node1.next;
}
while(node2 != null){
if (nodeHashMap.containsKey(node2)){
return node2;
}
node2 = node2.next;
}
return null;
}
/**
* hashSet方法解析 两个链表,找出它们的第一个公告子节点
* @param listNode1
* @param listNode2
* @return
*/
public static ListNode hhztFindFirstCommonNodeBySet(ListNode listNode1,ListNode listNode2){
Set<ListNode> listNodeSet = new HashSet<ListNode>();
ListNode node1 = listNode1;
ListNode node2 = listNode2;
while(node1 != null){
listNodeSet.add(node1);
node1 = node1.next;
}
while(node2 != null){
if ( listNodeSet.contains(node2)){
return node2;
}
node2 = node2.next;
}
return null;
}
/**
* 简单构造两个链表,直接引用数据
*
* @return
*/
private static ListNode[] initLinkedList() {
ListNode[] heads = new ListNode[2];
// 构造第一个链表交点之前的元素 1 ->2-> 3
heads[0] = new ListNode(1);
ListNode current1 = heads[0];
current1.next = new ListNode(2);
current1 = current1.next;
current1.next = new ListNode(3);
current1 = current1.next;
// 构造第二个链表交点之前的元素11->22
heads[1] = new ListNode(11);
ListNode current2 = heads[1];
current2.next = new ListNode(22);
current2 = current2.next;
// 构造公共交点以及之后的元素
ListNode node4 = new ListNode(4);
current1.next = node4;
current2.next = node4;
ListNode node5 = new ListNode(5);
node4.next = node5;
ListNode node6 = new ListNode(6);
node5.next = node6;
return heads;
}
栈的基本用法:
push方法:添加元素
当栈为空的时候,top 为 -1 ,当只有 1 个元素时,top 为 0
stack.push()
pop方法:弹出栈顶元素
stack.pop()
peek方法:获取栈顶元素
stack.peek()
代码如下(示例):
/**
* 栈 方法解析 两个链表,找出它们的第一个公告子节点
* @param listNode1
* @param listNode2
* @return
*/
public static ListNode hhztFindFirstCommonNodeByStack(ListNode listNode1,ListNode listNode2){
Stack<ListNode> listNodeStack = new Stack<ListNode>();
Stack<ListNode> listNodeStack2 = new Stack<ListNode>();
ListNode node1 = listNode1;
ListNode node2 = listNode2;
while(node1 != null){
listNodeStack.push(node1);
node1 = node1.next;
}
while(node2 != null){
listNodeStack2.push(node2);
node2 = node2.next;
}
ListNode nodeNew = null;
//错误,忘记进行循环比较
// if (listNodeStack.peek()==listNodeStack2.peek()){
// nodeNew = listNodeStack.pop();
// listNodeStack2.pop();
// }
while(listNodeStack.size()>0&&listNodeStack2.size()>0){
//需要在加深理解下,栈顶数据
if (listNodeStack.peek()==listNodeStack2.peek()){
nodeNew = listNodeStack.pop();
listNodeStack2.pop();
}else {
break;
}
}
return nodeNew;
}
首先理解什么是回文序列:
如果一个数字序列逆置之后跟原序列是一样的,就称这样的数字序列为回文序列。
例如:
{1, 2, 1}, {15, 78, 78, 15} , {112} 是回文序列,
{1, 2, 2}, {15, 78, 87, 51} ,{112, 2, 11} 不是回文序列。
判断链表是否为回文序列
示例:
输入:1->2->2->1
输出:true
进阶:你能否用O(n)时间复杂度和O(1)空间复杂度解决此题?
package com.yugutou.charpter1;
import com.yugutou.charpter1_linklist.level2.topic2_2回文序列.IsPalindromic;
import java.util.Stack;
/**
* 判断链表是否为回文序列
* 示例:
* 输入:1->2->2->1
* 输出:true
* 进阶:你能否用O(n)时间复杂度和O(1)空间复杂度解决此题?
*/
public class HhztIsPalindromic {
public static void main(String[] args) {
int[] a = {1, 2, 3, 4, 4, 3, 2, 1};
ListNode node = initLinkedList(a);
//全部压栈
boolean b = hhztIsPalindromeByAllStack(node);
System.out.println(b);
}
static class ListNode{
public int val;
public ListNode next;
public ListNode(int a){
val = a;
next = null;
}
}
public static ListNode initLinkedList(int[] array){
//错误的初始化
// ListNode head =null;
// for (int i = 1; i < array.length; i++) {
// head = new ListNode(i);
// if (i==1){
// head.val = i;
// }else {
// head = head.next;
// }
// }
ListNode head =null,cur =null;
for (int i = 0; i < array.length; i++) {
ListNode newNode = new ListNode(array[i]);
if (i==0){
head = newNode;
cur = head;
}else{
cur.next = newNode;
cur = newNode;
}
}
return head;
}
/**
* 全部压栈 判断链表是否为回文序列
*/
public static boolean hhztIsPalindromeByAllStack(ListNode node){
//为什么不能使用listNode的泛型进行比较呢?
//Stack listNodeStack = new Stack();
Stack<Integer> listNodeStack = new Stack<Integer>();
ListNode head = node;
while (head !=null){
listNodeStack.push(head.val);
head = head.next;
}
//如何将栈中的元素进行对比呢? 思路陷入盲区,其实思考下栈的原理就能明白,先入后出,
// 栈目前的顺序是和原始的链表顺序是相反的,我们可以进行直接出栈对比
while (node!=null){
//将链表的每个数据与另外一组出栈的数据进行比较
if (node.val != listNodeStack.pop()){
return false;
}
node = node.next;
}
return true;
}
/**
* 压栈一半 判断链表是否为回文序列
* 与全部压栈的主要区别是 Java中>>,>>=,<<,<<=运算符的使用方式
* @param node
* @return
*/
public static boolean hhztIsPalindromeByHalfStack(ListNode node){
Stack<Integer> listNodeStack = new Stack<Integer>();
ListNode head = node;
//创建一个计数的参数
int count =0;
while (head !=null){
listNodeStack.push(head.val);
head = head.next;
count++;
}
//首先我们要了解 Java中>>,>>=,<<,<<=运算符的作用
/**
1.Java语言中的 >> 意思为:逻辑右移,相当于除以2
如:8>>1、8>>2
8>>1,则是将数字8右移1位,结果为8/=4
8>>2,则是将数字8右移2位,结果为8/=2
2.Java语言中的 >>= 意思为:右移后赋值,相当于除以2
如:当x = 8时,
x >>= 2,则是x = 8/,结果x =2
x >>= 3,则是x = 8/,结果x =1
3.Java语言中的 << 意思为:逻辑左移,相当于乘2
如:2<<1、3 << 2、3<<3
2 << 1,则是将数字2左移1位,结果为2*=4
3 << 2,则是将数字3左移2位,结果为3*=12
3 << 3,则是将数字3左移3位,结果为3*=24
4.Java语言中的 <<= 意思为:左移后赋值,相当于乘2后赋值
如:当x = 5时,
x <<= 3,则是x = 5*,结果x = 40
————————————————
版权声明:本文为CSDN博主「Java大数据运动猿」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/m0_51697147/article/details/128601518
*/
//计数的长度除以2
count >>=1;
//如何将栈中的元素进行对比呢? 思路陷入盲区,其实思考下栈的原理就能明白,先入后出,
// 栈目前的顺序是和原始的链表顺序是相反的,我们可以进行直接出栈对比
while (count-- >=0){
//将链表的每个数据与另外一组出栈的数据进行比较
if (node.val != listNodeStack.pop()){
return false;
}
node = node.next;
}
return true;
}
}
判断链表是否为回文序列的实现方式,双指针,递归