!!!排列组合问题 古典概型现在面试的主趋势
单向链表中最重要的是节点,真正有用的值是value,next是维持结构用的
- 1 第一个节点有特殊性,1)没有前驱2)代表整个链表
- 2 插入删除节点时需要前驱节点,除非是第一个节点
- 3 凡是解引用的地方,需要考虑引用是否为null
- 4 方便断开方便接上
public class LinkedNode {
/**
* @Description: 删除链表中的指定元素{1.考虑指定元素包含了head的情况}
* 1.
* 定义新链表:Node head表示原链表的head;Node result 结果链表;
* Node last 记录最后一个节点,可以提升尾插的性能
* 一次遍历链表中的每一个节点,如果节点的值不是要删除的值,把节点尾插到新链表中
* 2.遍历链表,若果是指定指就删除
*/
public LinkedNode next;
public int value;
public LinkedNode(int value) {
this.value = value;
}
public LinkedNode removeElements(LinkedNode head, int value) {
//1.定义新链表的头节点
LinkedNode newHead = null;
LinkedNode cur = head;
LinkedNode next = null;
//遍历原链表
while (cur != null) {
//如果不是指定元素,就尾插到新列表
if (cur.value == value) {
//如果是指定元素,遍历到下一个尾插
next = cur.next.next;
newHead = next;
// cur=cur.next.next;
} else {
//尾插
next = cur.next;
newHead = next;
// cur=cur.next;
}
if (cur == head) {
newHead = head;
}
newHead.next = null;
cur = next;
}
return newHead;
}
/**
* @Description: 反转单链表:定义一个新链表,取源链表中的每一个节点,头插到新链表的节点中
* @Param:
* @return:
*/
public LinkedNode reversList(LinkedNode head) {
//定义结果链表
LinkedNode result = null;
LinkedNode cur = head;
//进行头插操作
// 1-2-3-4
//1-null;2-1-null;
while (cur != null) {
LinkedNode next = cur.next;
//前插操作:
// 1.将当前取到的节点作为结果链表的头元素,头插进去(node.next=head;this.head=node;)
// 2.当前元素就成为了结果链表
cur.next = result;
result = cur;
//使循环继续,利用cur取下一个节点
cur = next;
}
return result;
}
/**
* @Description: 反转链表2:利用三引用反转
* @Param: LinkedNode pre=null;LinkedNode cur=head;LinkedNode next=cur.next;
* @return:
*/
public LinkedNode reverseList2(LinkedNode head) {
if (head == null) {
return null;
}
LinkedNode pre = null;
LinkedNode cur = head;
//1-2-3-4-2-3
// p c n
//3-2-1-null
while (cur != null) {
LinkedNode next = cur.next;
//逆置的过程
cur.next = pre;
pre = cur;
//循环向后走的条件
cur = next;
}
return pre;
}
/**
* @Description: 合并两个有序链表:定义一个新链表:分别遍历两个链表,选择一个值较小的尾插进新链表
* @Param: LinkedNode next=cur.next; LinkedNode result=null;LinkedNode last=null;
* @return:
*/
public LinkedNode mergeTwoList(LinkedNode l1, LinkedNode l2) {
if (l1 == null) {
return l2;
}
if (l2 == null) {
return l1;
}
LinkedNode cur1 = l1;
LinkedNode cur2 = l2;
LinkedNode last = null;
LinkedNode result = null;
while (cur1 != null && cur2 != null) {
if (cur1.value >= cur2.value) {
LinkedNode next = cur2.next;
cur2.next = null;
//取值小的(cur2)尾插操作
if (result == null) {
result = cur2;
} else {
last.next = cur2;
}
last = result;
cur2 = next;
} else {
LinkedNode next = cur1.next;
cur1.next = null;
if (result == null) {
result = cur1;
} else {
last.next = cur1;
}
last = result;
cur1 = next;
}
}
return result;
}
/**
* @Description: 以给定x为基准将链表分割为两部分,
* 所有小于x的节点排在大于或=x的节点前,节点的排列顺序不改变
* <p>
* 解决思路:定义两个链表:遍历原链表,小于x的节点就放在小的新链表中,
* 大于=x的就放在大的链表中,再把两个链表链接即可
*/
public LinkedNode partation(LinkedNode pHead, int x) {
LinkedNode small = null;
LinkedNode smallLast = null;
LinkedNode big = null;
LinkedNode bigLast = null;
LinkedNode cur = pHead;
while (cur != null) {
LinkedNode next = cur.next;
if (cur.value < x) {
//保证尾插的最后一个节点的next为空
cur.next = null;
//如果小链表没有头节点,当前就是小链表的头节点
if (small == null) {
small = cur;
} else {
//否则就就最后一个节点的下一个,即尾插
smallLast.next = cur;
}
smallLast = cur;
cur = next;
} else {
cur.next = null;
if (big == null) {
big = cur;
} else {
bigLast.next = cur;
}
bigLast = cur;
cur = next;
}
}
//将大链表尾插到小链表中{ 1.small为null || 2.big为null}
LinkedNode result = null;
if (small == null) {
return big;
} else {
smallLast.next = big;
return small;
}
}
/**
* @Description: 给定一个带有节点head的链表,返回链表的之间节点,如果有两个节点,取第二个节点
* 解决:双指针遍历:一个指针跑两步,另一个指针只跑一步,
* 因此快的指针走到null节点(末尾)了,慢的指针正好在链表之间
*/
public LinkedNode middleNode(LinkedNode head) {
LinkedNode fast = head;
LinkedNode slow = head;
while (fast != null) {
fast = fast.next;
if (fast == null) {
break;
}
slow = slow.next;
//fast走了两步,slow只走了一步,
// 并且保证若是偶数个节点,慢指针值向的是中间的后一个节点
fast = fast.next;
}
return slow;
}
/**
* @Description: 在非空单链表中找指定倒数第K个节点
* 解决:定义一前以后两个节点,先让前一个节点先走K步,然后一起前进,
* 当前面的节点走到null,则就是倒数第K个节点
* 考虑特殊情况{链表正好有m个节点,1.指定k大于m: 2.k等于m}
* 1-2-3-4-5
* @return:
*/
public LinkedNode findKthNode(LinkedNode head, int k) {
LinkedNode front = head;
LinkedNode back = head;
int i = 0;
for (i = 0; i < k; i++) {
front = front.next;
}
//如果链表有5个节点,要找倒数第六个
if (front == null && i < k) {
return null;
//如果链表有5个节点,要找倒数第五个
} else if (front == null) {
return head;
}
while (front != null) {
front = front.next;
back = back.next;
}
return back;
}
/**
* @Description: 对于一个链表,设计一个时间复杂度为O(N),额外空间复杂度为O(1)的算法,
* 判断其是否为回文结构 (ABCCBA)
* 解决:遍历链表,得到链表长度,然后找到中间节点,将中间节点以后的链表逆置,
* 再与新链表逐个比较,一旦有不同的节点就说明不是回文
*/
public boolean chkPalindrome(LinkedNode A) {
LinkedNode middle = A;
int len = getLenght(A);
for (int i = 0; i < len / 2; i++) {
//得到之间节点
middle = middle.next;
}
middle = reversList(middle);
while (A != null && middle != null) {
if (A.value != middle.value) {
return false;
}
A = A.next;
middle = middle.next;
}
return true;
}
private int getLenght(LinkedNode a) {
int len = 0;
while (a != null) {
len++;
a = a.next;
}
return len;
}
/**
* @Description: 在一个排序链表中,存在重复的节点,删除链表中重复的节点,
* 重复的节点不保留,返回链表头指针
* 解决:两个指针一前一后,若不相等则一起向前走,
* 若相等,则前面的节点继续向前走,走到不相等或为空为止,将第二个节点尾插到第一个节点
* 还需引入一个prev=null,申请一个假节点dummy,作为处理后的链表的头节点
* @return: dummy.next
*/
public LinkedNode deleteDuplication(LinkedNode head) {
//p1,p2是进行比较的两个节点
LinkedNode p1 = head;
LinkedNode p2 = head.next;
//假节点用来消除第一个节点没有前驱的特殊性(如果第一个节点开始就重复便无法处理)
LinkedNode dummy = new LinkedNode(0);
dummy.next = head;
//pre永远是p1的前一个,用来删除节点
LinkedNode pre = dummy;
//1-2-3-4-4-4-5-6
//如果值不相等则往前走
if (head == null) {
return null;
}
while (p2 != null) {
//因为有序,p1和p2不相等,和p2的next更不会相等,因此比较可以向前继续
if (p1.value != p2.value) {
pre = pre.next;
p1 = p1.next;
p2 = p2.next;
} else {
//若果相等则p2向前走,走到不相等为止
while (p2.value == p1.value) {
p2 = p2.next;
}
//将p2指向的节点尾插到pre上
pre.next = p2;
p1 = p2;
p2 = p2.next;
}
}
return dummy.next;
}