1:删所有key
public class LinkOj1 {
//删除所有的key
public static void main(String[] args) {
}
}
class ListNode {
int val;
ListNode next;
ListNode() {
}
ListNode(int val) {
this.val = val; }
ListNode(int val, ListNode next) {
this.val = val; this.next = next; }
}
class Solution {
public ListNode removeElements(ListNode head, int val) {
// 返回的是头节点?
if(head==null){
return null;
}
ListNode cur=head;
// 遍历一遍;遇到值就处理
while (cur.next!=null){
if(cur.next.val==val) {
cur.next = cur.next.next;
}
else {
cur=cur.next;
}
}
return head;
}
}
2:反转链表
class Solution {
public ListNode reverseList(ListNode head) {
ListNode cur = head;
head=null;//为了在第一次的时候;cur.next为空;最后节点为null
while (cur != null) {
//更新cur往后走;得记住后面节点的地址;不能直接在后面cur=cur.next;因为cur.next已经被改成head了
ListNode Node=cur.next;
//替换的语句;把当前节点的next换成上一个的地址。走到最后;刚好head到新头的位置
cur.next=head;
//这是更新的语句;head往后走;cur也忘后走
head=cur;
cur=Node;
}
return head;
}
}
总结:这题需要用到三个节点;head;cur;Node;head是头节点;cur是用来遍历的节点(我们之前写的都是cur=cur.next;但是现在问题在于我们遍历到节点的时候需要把当前节点的next修改成前一个的地址;也就是起到掉头反转的效果。。但是引入问题我们把当前节点的next修改了;我们就没法继续往下遍历;就需要Node在修改前先把这个当前节点的next记录下来;用来代替后面cur=cur.next——>cur=Node )
往下走的问题解决了;但是上一个节点的地址我们又丢了;可以使用新创建一个节点保存这个上一个节点的位置。但是head也是需要往后走;或者是走到最后时把最后节点的地址赋给head。所以可以一举两得;用head记录上一个的位置;还能往下走更新。但是这里的开始是head和cur是保持在同一个位置;所以我们走慢一步;在cur更新前;将cur赋值给head。
第三题:返回中间节点
返回中间节点
这种粗暴的方法;虽然多少有点丑陋;但是还是管用的。遍历一遍找
class Solution {
public ListNode middleNode(ListNode head) {
ListNode cur=head;
int count=0;
while(cur!=null){
count++;
cur=cur.next;
}
cur=head;
// 因为cur是从head开始;所以要走count-1步;或者我们干脆条件还是小于0;然后中间节点就不加1;因为开始的位置是从head开始。我们上面算是走多了一步
if(count%2==1){
int a=count/2+1;
while(a>1){
cur=cur.next;
a--;
}
return cur;
}
else{
int b= count/2+1;
while(b>1){
cur=cur.next;
b--;
}
return cur;
}
}
}
快慢指针:一个指针走一步;一个指针走两步;当走两步的指针找到终点的时候;另一个就是刚好到中间节点。而且这种情况也能避免是偶数个节点的冲突情况;但是我们需要注意特殊情况;这种是否能适用呢?
比如:只有一个节点;或者没有节点。
class Solution {
public ListNode middleNode(ListNode head) {
if(head==null){
return null;
}
if(head.next==null){
return head;
}
ListNode Node1=head;
ListNode Node2=head;
while(Node2.next!=null){
//使用Node2.next而不使用;Node2;是为了避免;cur如果在最后一个节点进入时;往下走两步又空指针异常
Node1=Node1.next;
//走两步没问题;但是说走到最后一步;后面没东西;next一次是空;再next一次;null的next肯定就空指针异常
Node2=Node2.next.next;
//但是如果不加以下判断;当走到这里为空;继续循环判定;Node2.next就空指针异常了
if( Node2==null){
return Node1;
}
}
return Node1;
}
}
第四题:倒数第k个节点
倒数第k个节点
方法1:走len-k下就是倒数第k个节点;先求len;再遍历找到。
public class Solution {
public ListNode FindKthToTail(ListNode head,int k) {
ListNode cur=head;
if(head==null){
return null;
}
int len=0;
while(cur!=null){
len++;
cur=cur.next;
}
cur=head;
int count=len-k;
//非法情况;k比长度节点个数还大。因为我们的代码结构;所以这里必须处理;如果不处理返回cur不是null;返回的是head了。
if(count<0){
return null;
}
while(count >0){
cur=cur.next;
count--;
}
return cur;
}
}
方法2:快慢指针;a先走k下;然后b和a一起走;b还在开头;直到a到末尾;b就是倒数第k个节点。当有思路;还是得考虑特殊情况能不能适合你这个思路
class Solution {
public ListNode FindKthToTail(ListNode head,int k) {
ListNode cur1=head;
ListNode cur2=head;
if(head==null){
// null如果进去循环就空指针异常
return null;
}
if(k<=0){
return null;
}
//等于0要不要进去?简单画个图举例分析一下即可;走k步不需要。假设k=1;是否真的走了一步。
while(k>0){
cur1=cur1.next;
// 这里的情况是k比节点个数还大;但是我们如果光是cur1==null是不够这个条件;如果k和节点个数是一样大的 cur1==null就能符合。所以得加个判定;剔除这种情况。这种情况这里k是等于1的;下一轮循环就进不来
if(cur1==null&&k!=1){
return null;
}
k--;
}
while(cur1!=null){
cur1=cur1.next;
cur2=cur2.next;
}
return cur2;
}
}
第五题:合并两个有序链表
合并两个有序链表
到底在拼在原来的某一个链表上;还是创建一个新的依次插入即可了???题目是说新链表
class Solution {
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
// 创建一个新链表;头节点还不能移动。。还得注意;我们交换的是节点;而不是节点的值;最后拼接链表;只需要原来的最后一个节点的下一个接成要拼接链表的头节点
ListNode newlist=new ListNode(0);
ListNode newhead=newlist;
ListNode head=newhead;
// list1和list2就是头节点。
//这里的结束条件都有一个链表的元素全部放人后就结束;另一种直接插入后面即可
while(list1!=null&&list2!=null){
if(list1.val>=list2.val){
head.next=list2;
head=head.next;
list2=list2.next;
}
else{
head.next=list1;
head=head.next;
list1=list1.next;
}
}
// 走到这里1:一开始两个链表都为空;2:其中一个一开始就为空;3:进行插入新链表后的位置走到空的节点......都是一样把不空的直接拼接到后面去
if(list1==null){
head.next=list2;
}
else{
head.next=list1;
}
return newhead.next;
}
}
因为这里的头节点是不知道的;头节点可不能引用别人的地址;不能修改;一改头就变了;链表也变成别人的。
只能修改头的next;头一修改;就没了;哪怕你搞个新节点赋值给头;然后再修改;一改就变成别的链表了。就找不到这个我们新的刚创建的链表。
第六题:链表分割
public class Partition {
public ListNode partition(ListNode pHead, int x) {
// write code here
// 两个哨兵节点
if(pHead==null){
return null;
}
ListNode small=new ListNode(0);
ListNode head1=small;
ListNode big=new ListNode(0);
ListNode head2=big;
ListNode cur=pHead;
// 这一步没必要;因为我们已经不需要用到原来链表;所以干脆用它的头结点去跑也可以
//这里使用的方法;两个哨兵节点;借鉴上面的合并有序链表;第一个空出来;都放下一个里;最后返回头的next。
while(cur!=null){
if(cur.val<x){
small.next=cur;
small=small.next;
cur=cur.next;
}
else{
big.next=cur;
big=big.next;
cur=cur.next;
}
}
small.next=head2.next;
//一定要注意;把链表的尾部的next设置为null
big.next=null;
return head1.next;
}
}
第七题:链表回文结构
链表回文结构
什么是回文结构? 回文结构序列是 指顺读和反读都一样的序列。
如果不给定时间复杂度和空间复杂度要求就有很大的发挥空间。
{
{
{
{
{
{
{
{
{
{
{
{
{
{
{
{
{
{
{
{
{
{
{
{
{
{
{
{
{
{
{
{
{
{
{
{
想法1:把链表遍历一遍;把val值存在数组里;再判定数组里的数据是不是回文。因为数组对下标操作能操作随心所欲的操作。但是显然不符合题意。
想法2:定义两个指针;一个往前走;一个从尾走。但是这里要找到尾也不容易。但是这里是单链表;咋往前走???这种行不通的。
想法3:逆序后和原来比较;但是逆序后就是新链表;空间复杂度就O(n)
还有一个想法:我们先遍历到中间节点;在这个遍历的过程;将前面的进行头插;组成一个新链表。然后就可以和后半段的遍历进行比较。(得是头插才能是顺序一样)
}}}}}}}}}}}}}}}}}}}}}
解法:两个指针遍历走到中间;然后一个往前走;一个往后走;分别看它们的值是否相等。
怎么走到中间这也是一个问题;快慢指针?一个走两步;一个走一步;然后两个都赋值于中间节点一个往前一个往后。
(没法往前走是问题;那没关系;我把其中一个中间节点重新赋予头节点地址;那就从头往中间走;但是比较的顺序却反了)
但是这样子也有解决办法;假如12 21.我们就把后面的反转过来。然后一边从头开始;一边从中间开始;
总结:快慢指针;找到中间节点;然后把后半段进行反转。然后前半段从头开始遍历;后半段从尾开始遍历;两两比较。
分三部分:
1:快慢指针找到中间节点;
快慢指针的两种写法
ListNode cur1=A;
ListNode cur2=A;
while(cur2.next!=null){
cur1=cur1.next;
cur2=cur2.next.next;
if(cur2==null){
break;
}
//循环的条件必须用cur2.next;不然就会出现空指针异常。假设使用的是cur!=null;当走到cur.next=null;即最后一个节点;cur2=cur2.next.next;就空指针异常。
//一定要加这个判断;假设走到倒数第而个节点;然后cur2=cur2.next.next;这时cur2是不是就为null;而我们的循环判断条件是cur.next!=null;这里又会发送空指针异常
//知道这里中间节点是哪一个;如果是奇数节点;那不必都说;如果是偶数节点个数。画个图就知道是在上半部分的最后一个节点。
}
另一种写法:这里利用一个语法&&只要第一个不符合条件;然后后面的代码就无效;所以这里cur2=null时;cur2.next是已经无效了;不会发生空指针异常;两种本质上是一样的
2:反转链表:就和第二题思路一样即可;但是这里可以省略一步;把开始头的next置空;因为我们都使用不到这个链表
3:链表遍历:遍历链表;如果都相等就是回文;如果不相等就不是回文。因为反转过后中间节点就跑到最后面去了;后面我们需要后面往前遍历。值不相等就直接返回不是回文;.结束条件就是前面的节点和后面的节点相遇就说明是回文。
class PalindromeList {
public boolean chkPalindrome(ListNode A) {
// write code here
if(A==null||A.next==null){
return false;
}
ListNode cur1=A;
ListNode cur2=A;
while(cur2.next!=null){
cur1=cur1.next;
cur2=cur2.next.next;
if(cur2==null){
break;
}
}
// while(cur2!=null&&cur2.next!=null){
// cur2=cur2.next.next;
// cur1=cur1.next;
// }
// 反转后面的链表
ListNode cur=cur1;
cur1=null;
while(cur!=null){
ListNode Node=cur.next;
cur.next=cur1;
cur1=cur;
cur=Node;
}
//走到这里我们需要记住A是前部分链表头节点;cur1是反转后的后半部分头节点。
while(A!=cur1){
//这里外面的A!=cur1是奇数个节点的情况;里面的A.next!=cur1是偶数个节点的情况
if(A.val!=cur1.val){
return false;
}
if(A.next==cur1){
return true;
}
//不加这个条件;如果是偶数个节点;没法结束。
A=A.next;
cur1=cur1.next;
}
return true;
}
}
第八题:相交链表;找出第一个相交节点
相交肯定就是这种Y形状;不可能相交后后面还能分开;因为同一个节点它们值和指向都是一样。
解法一:遍历一遍链表A;储存在哈希表;然后遍历链表B看看是否能找到相同的节点
解法二:两种情况两个链表长度一样;或者不一样。
如果是长度一样;那好办;大家一起走;边走边比较就可以知道。
但是长度不一样就不好办;解决办法求各自链表的长度;这个差值一定就是相遇之前的差值;那不就意味着这个差值是多余的嘛;我们让长链表先把这个差值走完;就能转化为第一种情况。(不可能在这些差值的前面相遇;不然它们的长度就不会差别这么多)
class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode small=headA;
ListNode big=headB;
int countA=0;
int countB=0;
while(small!=null){
countA++;
small=small.next;
}
while(big!=null){
countB++;
big=big.next;
}
// 到这里我就前面的变量不要就好了;因为已经变成null;或者利用起来;重新赋于头节点的值。
ListNode curA=headA;
ListNode curB=headB;
int len=countA-countB;
if(len<0){
int newlen=0-len;
//说明B比较长;然后这里就执行B走newlen步
while(newlen>0){
curB=curB.next;
newlen--;
}
// 到这里他们是共同起点
while(curB!=null){
if(curB==curA){
return curA;
}
curB=curB.next;
curA=curA.next;
}
}
else{
//说明A比较长;直接A先走len步
while(len>0){
curA=curA.next;
len--;
}
// 到这里他们是共同起点
while(curB!=null){
if(curB==curA){
return curA;
}
curB=curB.next;
curA=curA.next;
}
}
return null;
}
}
上述代码会发现有点重复了;因为谁长谁短我们并不清楚;所以导致要分if else写两份一样逻辑的代码;
优化:我们定义两个变量;一个永远指向短的链表;一个永远指向长的链表。使用这两个变量我们在遍历完计数长度后。这两个都变成null。然后我们根据len 大于0还是小于0;来分别重新给这两个变量赋值;按照small是放短的;big是放长的。后面的逻辑就是写一份就能通用。
ListNode small=headA;
ListNode big=headB;
第九题:判断链表是否有环
判断链表是否有环
环是什么呢?如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。
思路1:追击的数学问题;一个环的情况;如果我一直走是不是就会有走重复的地方。一直在转圈圈。
假设:老师从课室去操场散步;我想去问问题;如果只有我自己一个;老师突然去饭堂了;那光我一个人在操场转圈圈也找不到老师。所以就需要两个指针去跑才能有机会相遇。
那么之间的步数怎么设置好了;是老师走一步我走两步好呢;还是老师走一步我走三步呢?必须是两步。
假设是三步:下面这种情况;操场非常小;小到我一步就是一圈;那么我和老师就一直相遇不了。
设置两步最坏情况一个环的长度就能相遇。中间间隔大;相遇概率很低
结束条件;fast为空就说明没有环;
public class Solution {
public boolean hasCycle(ListNode head) {
ListNode cur1=head;
ListNode cur2=head;
while(cur2!=null&&cur2.next!=null){
cur1=cur1.next;
cur2=cur2.next.next;
if(cur1==cur2){
return true;
}
}
return false;
}
}