首页 > 编程语言 >算法

算法

时间:2024-05-08 09:04:52浏览次数:27  
标签:head ListNode val aHead int next 算法

01-链表

01.移除链表元素

迭代法

注意:

  1. head可能为null,也可能是要删除的val;因此要设置一个虚拟头结点指向头结点,再通过迭代一个一个删除。
  2. 删除方式为:循环,判断下一节点是否是目标节点,是则指向目标节点的下一节点(这里不要移动指针,因为删除后的下一节点仍然有可能是目标节点)。不是则将指针后移。

代码:

class Solution {
    public ListNode removeElements(ListNode head, int val) {
        ListNode remo = new ListNode(0);
        remo.next = head;
        ListNode tmp = remo;
        while(tmp.next != null){
            if(tmp.next.val == val){
                tmp.next = tmp.next.next;
            }else{
                tmp = tmp.next;
            }
        }
        return remo.next;
    }
}

02.设计链表

单链表

  • 自定义链表节点
  • 设计链表中,包含链表大小及虚拟头结点
  • MyLinkedList() 初始化链表,设置初始值
  • void addAtIndex(int index, int val) 此处index与其他添加方法不同,index可以等于size,这种情况就是插入尾节点。
public class design {
//测试主方法
    public static void main(String[] args) {
        MyLinkedList obj = new MyLinkedList();
        obj.addAtHead(7);
        obj.addAtHead(2);
        obj.addAtHead(1);
        obj.addAtIndex(3, 0);
        obj.deleteAtIndex(2);
        obj.addAtHead(6);
        obj.addAtTail(4);
        int a = obj.get(4);
        System.out.println(a);//4
        obj.addAtHead(4);
        obj.addAtIndex(5, 0);
        obj.addAtHead(6);
    }
}
//自定义节点类
class LinkedNode{
    int val;
    LinkedNode next;
    LinkedNode(){}
    LinkedNode(int val){
        this.val = val;
    }
}
//设计链表
class MyLinkedList{
    int size;//链表大小
    LinkedNode head;//虚拟头结点
    //初始化链表
    public MyLinkedList(){
        size = 0;
        head = new LinkedNode(0);
    }
    //获取指定下标元素
    public int get(int index){
        if(index < 0 || index > size - 1){
            return -1;
        }
        
        LinkedNode aHead = head;
        for(int i = 0; i <= index; i++){
        	aHead = aHead.next;
        }
        return aHead.val;
    }
	//在头部插入节点
    public void addAtHead(int val){
        LinkedNode node = new LinkedNode(val);
        LinkedNode aHead = head;
        node.next = aHead.next;
        aHead.next = node;
        size++;
    }
	//在尾部插入节点
    public void addAtTail(int val) {
        LinkedNode aHead = head;
        LinkedNode node = new LinkedNode(val);
        while(aHead.next != null){
            aHead = aHead.next;
        }
        aHead.next = node;
        size++;
    }
	//在指定索引插入元素
    public void addAtIndex(int index, int val) {
        //index可以等于size
        if(index < 0 || index > size){
            return;
        }
        int i = 0;
        LinkedNode aHead = head;
        LinkedNode node = new LinkedNode(val);
        while(i < index){
            aHead = aHead.next;
            i++;
        }
        node.next = aHead.next;
        aHead.next = node;
        size++;
    }
	//删除索引处元素
    public void deleteAtIndex(int index) {
        if(index < 0 || index > size - 1){
            return;
        }
        int i = 0;
        LinkedNode aHead = head;
        while(i < index){
            i++;
            aHead = aHead.next;
        }
        aHead.next = aHead.next.next;
        size--;
    }
}

双链表

  • 节点包含next、prev双向指针
  • 创建链表时,创建虚拟头结点和虚拟尾节点
class LinkedNode{
    int val;
    LinkedNode next, prev;
    LinkedNode(){}
    LinkedNode(int val){this.val = val;}
}

class MyLinkedList{
    int size;
    LinkedNode head, tail;
    MyLinkedList(){
        //初始化
        size = 0;
        head = new LinkedNode(0);
        tail = new LinkedNode(0);
        head.next = tail;
        tail.prev = head;
    }

    public int get(int index){
        if (index < 0 || index >= size){
            return -1;
        }
        LinkedNode aHead = head;
        LinkedNode aTail = tail;
        if(index < size / 2){
            for (int i = 0; i <= index; i++) {
                aHead = aHead.next;
            }
            return aHead.val;
        } else{//4, 3
            for (int i = size; i > index; i--) {
                aTail = aTail.prev;
            }
            return aTail.val;
        }
    }

    public void addAtIndex(int index, int val){
        if(index > size){
            return;
        }
        LinkedNode node = new LinkedNode(val);
        LinkedNode aHead = head;
        //找到前驱
        for (int i = 0; i < index; i++) {
            aHead = aHead.next;
        }
        node.next = aHead.next;
        aHead.next.prev = node;
        node.prev = aHead;
        aHead.next = node;
        size++;
    }

    public void addAtHead(int val) {
        //等价于在第0个元素前添加
        addAtIndex(0,val);
    }

    public void addAtTail(int val) {
        //等价于在最后一个元素(null)前添加
        addAtIndex(size,val);
    }

    public void deleteAtIndex(int index) {
        //判断索引是否有效
        if(index<0 || index>=size){
            return;
        }
        //删除操作
        size--;
        LinkedNode pre = this.head;
        for(int i=0; i<index; i++){
            pre = pre.next;
        }
        pre.next.next.prev = pre;
        pre.next = pre.next.next;
    }


}

03.反转链表

双指针法

cur指针指向head结点,pre指针指向null。将cur.next 指向null。再将两个指针向右平移。

img

import java.util.Scanner;

public class reverse {
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        ListNode head = new ListNode();
        //fprint(head);
        ListNode aHead = head;
        String str = scan.nextLine();
        String[] s = str.split(" ");
        for (int i = 0; i < s.length; i++) {
            int num = Integer.valueOf(s[i]);
            ListNode newNode = new ListNode(num);
            aHead.next = newNode;
            aHead = aHead.next;
        }
        fprint(head.next);
        head = reverseList(head);
        fprint(head);
    }
	//打印链表方法
    static void fprint(ListNode head){
        while(head != null){
            System.out.print(head.val + " ");
            head = head.next;
        }
    }
    //反转链表***
    static ListNode reverseList(ListNode head) {
        ListNode tmp;//临时记录cur.next
        ListNode cur = head.next;//双指针-前指针
        ListNode pre = null;//双指针-后指针
        while(cur != null){
            tmp = cur.next;
            cur.next = pre;
            //先后顺序不能变
            pre = cur;
            cur = tmp;
        }
        return pre;
    }

}
//节点定义
class ListNode {
      int val;
      ListNode next;
      ListNode() {}
      ListNode(int val) { this.val = val; }
      ListNode(int val, ListNode next) { this.val = val; this.next = next; }

}

递归法

思路与双指针法类似,写法不同。

static ListNode reverseList(ListNode head) {
    return reverse(null, head);
}

static ListNode reverse(ListNode pre, ListNode cur){
        if (cur == null) return pre;
        ListNode tmp = cur.next;
        cur.next = pre;
        return reverse(cur, tmp);
    }

04.两两交换链表中的节点

题目链接:24. 两两交换链表中的节点 - 力扣(LeetCode)

思路:

设置虚拟头结点,交换后两个节点,再将虚拟头节点向后移动两位继续做交换。

24.两两交换链表中的节点3

代码:

class Solution {
    public ListNode swapPairs(ListNode head) {
        ListNode cur = new ListNode();
        cur.next = head;
        ListNode aHead = cur;

        while(aHead.next != null && aHead.next.next != null){
            //临时记录
            ListNode tmp = aHead.next;
            ListNode tmp1 = aHead.next.next.next;

            aHead.next = aHead.next.next;//步骤一
            aHead.next.next = tmp;//步骤二
            aHead.next.next.next = tmp1;//步骤三

            aHead = aHead.next.next;

        }
        ListNode result = cur.next;
        return result;
    }
}

05.删除链表倒数第n个节点

思路:

  1. 虚拟头结点,方便删除
  2. 双结点,设置fast和slow两个结点,要找到倒数第n个数,即与最后一个结点的后一位空结点相差n个距离。那么在最开始,先让fast移动n个位置,使得fast与slow相差n个结点,再让fast与slow一起移动,当fast移动到空结点,slow就是需要删除的结点。

img

  • 移动n+1个单位,是为了让slow指向目标结点的前一结点,方便后续删除

img

img

img

代码:

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode val = new ListNode(0);
        val.next = head;
        ListNode fast = val;
        ListNode slow = val;
        //移动n+1个单位
        for(int i = 0; i <= n; i++){
            fast = fast.next;
        }
        //fast移动到空结点以前,fast与slow一起移动
        while(fast != null){
            fast = fast.next;
            slow = slow.next;
        }
        //删除目标结点
        slow.next = slow.next.next;
        //返回头结点
        return val.next;
    }
}

06.链表相交

思路:

  1. 如果链表相交,则链表A与链表B的相交部分必然在尾部,且长度相等。
  2. 将两个链表的尾部对齐,将长链表头部多出来的部分直接跳过,再依次判断链表A与链表B的结点是否相等,相等则返回结点,如果没有符合的结点,则返回null。

代码:

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode curA = headA;
        ListNode curB = headB;
        int lenA = 0;
        int lenB = 0;
        //A链表长度
        while(curA != null){
            curA = curA.next;
            lenA++;
        }
        //B链表长度
        while(curB != null){
            curB = curB.next;
            lenB++;
        }
        curA = headA;
        curB = headB;
        //如果B长度>A长度
        if(lenB > lenA){
            //交换len
            int tmpLen = lenB;
            lenB = lenA;
            lenA = tmpLen;
			//交换cur结点
            ListNode tmpNode = curB;
            curB = curA;
            curA = tmpNode;
        }
		//gap是A与B相差的长度,使A与B对齐
        int gap = lenA - lenB;
        while(gap != 0){
            curA = curA.next;
            gap--;
        }
        while(curA != null){
            if(curA == curB){
                return curA;
            }
            curA = curA.next;
            curB = curB.next;
        }
        return null;

    }
}

07.环形链表

思路:

  • 判断是否有环,找到环的入口
  • 如何判断是否有环:设置快慢双指针,快指针每次移动两个结点,而慢指针每次移动一个结点,如果有环的话,快慢指针终会在环中的某个位置相遇,说明有环。
  • 如何找到环的入口

img

那么相遇时: slow指针走过的节点数为: x + y, fast指针走过的节点数:x + y + n (y + z),n为fast指针在环内走了n圈才遇到slow指针, (y+z)为 一圈内节点的个数A。

因为fast指针是一步走两个节点,slow指针一步走一个节点, 所以 fast指针走过的节点数 = slow指针走过的节点数 * 2:

(x + y) * 2 = x + y + n (y + z)

两边消掉一个(x+y): x + y = n (y + z)

因为要找环形的入口,那么要求的是x,因为x表示 头结点到 环形入口节点的的距离。

所以要求x ,将x单独放在左面:x = n (y + z) - y ,

再从n(y+z)中提出一个 (y+z)来,整理公式之后为如下公式:x = (n - 1) (y + z) + z 注意这里n一定是大于等于1的,因为 fast指针至少要多走一圈才能相遇slow指针。

这个公式说明什么呢?

先拿n为1的情况来举例,意味着fast指针在环形里转了一圈之后,就遇到了 slow指针了。

当 n为1的时候,公式就化解为 x = z

这就意味着,从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点

也就是在相遇节点处,定义一个指针index1,在头结点处定一个指针index2。

让index1和index2同时移动,每次移动一个节点, 那么他们相遇的地方就是 环形入口的节点。

142.环形链表II(求入口)

代码:

public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode fast = head;
        ListNode slow = head;
        while(fast != null && fast.next != null){
            fast = fast.next.next;
            slow = slow.next;
            if(slow == fast){//有环
                ListNode index1 = head;
                ListNode index2 = fast;
                while(index1 != index2){
                    index1 = index1.next;
                    index2 = index2.next;
                }
                return index1;
            }
        }
        return null;
        
    }
}

标签:head,ListNode,val,aHead,int,next,算法
From: https://www.cnblogs.com/forest-pan/p/18178881

相关文章

  • 算法竞赛第一章-队列
    1、队列constintN=1e5;//定义队列大小intque[N],head,tail;//队头队尾指针,队列大小为tail-head+1//head++;弹出对头,head<=tail//queue[head];//读对头数据//que[++tail]=data;//数据data入队,尾指针加1,注意不能溢出2、STLqueuequeue<Type>q:定义......
  • 常见的排序算法——归并排序(二)
    本文记述了自底向上归并排序的基本思想和一份参考实现代码,并在说明了算法的性能后用随机数据进行了验证。◆思想使用自底向上的递推思想进行排序。从大小为1的子范围开始两两归并,得到小规模排序的结果。逐步将子范围的大小翻倍并继续两两归并,直到整个数组范围都已被归并,即得......
  • leedcode-分发饼干(贪心算法)
    自己写的,没有使用排序,会超出时间限制:classSolution:deffindContentChildren(self,g:List[int],s:List[int])->int:iflen(s)==0:return0count=0foriinrange(len(g)):mydict={}forjin......
  • 算法学习笔记(16):Link Cut Tree
    LinkCutTree简称LCT(不是LiChaoTree),是一种非常强大的数据结构。声明该博客写来很大部分目的是帮助自己理解,笔者水平有限,没办法完全原创,有很多内容源自于OI-wiki,和网上博客,见谅。功能考虑一些问题:树上单点查,树上路径修改,这是树上差分可以解决的。那么如果路径查,......
  • 二叉树进阶:二叉搜索树、平衡二叉树、KD树(实现KNN算法的快速寻找k个邻居)
    二叉搜索树二叉搜索树又称为二叉排序树、二叉查找树。请记住,本篇所介绍的所有树,都是二叉搜索树,也就是说都用到了二分查找的思想。二叉搜索树的特征:每个结点的左结点都小于结点本身,右结点都大于结点本身。用中序遍历来遍历一棵二叉搜索树,结果必然是有序的。时间复杂度很低......
  • 洛谷P1576最小花费(逆Dijkstra算法)
    背景:说实话,这题有点考建模思想,及对Dijkstra算法的理解思路:因为转账间有手续费,即有一定损失比例,故边权均小于1(比例来说),而边的路权值非和,而是积,故边权相当于负(因为每次乘会使dis[i]变小)而题目刚好求最大路,而边权又等价于全为负,不刚好是Dijkstra的逆运用吗?原理:等......
  • AutoCAD C# 两不平行直线倒圆弧算法
    参考的博客:https://www.cnblogs.com/JJBox/p/14300098.html下面是计算示例主要计算代码:varpeo=newPromptEntityOptions("选择直线1"){AllowNone=false,AllowObjectOnLockedLayer=false};peo.SetRejectMessage("请选择直线Line");p......
  • 二叉树相关的三个常见算法题
    算法题一//计算一颗二叉树的所有节点的数量intBinaryTree_CountNode(Tnode_t*root){intn1,n2;if(NULL==root){return0;}n1=BinaryTree_CountNode(root->lchild);n2=BinaryTree_CountNode(root->rchild);returnn1+......
  • Diff算法
    概论:什么是Diff算法?为什么要使用diff算法?手写Vue的diff算法手写React的diff算法diff去比较虚拟DOM=》找出差异点(需要更新一个虚拟DOM)=》真实DOM=》renderVue中diff算法实现:https://blog.csdn.net/weixin_69422396/article/details/135475844React中diff算......
  • 游戏排名算法:Elo、Glicko、TrueSkill
    EloratingsystemElo等级分制度(英语:Eloratingsystem)是指由匈牙利裔美国物理学家ArpadElo创建的一个衡量各类对弈活动水平的评价方法,是当今对弈水平评估公认的权威标准。两个选手(player)在排名系统的不同,可以用来预测比赛结果。两个具有相同排名(rating)的选手相互竞争时,不管哪......