LeetCode(1)

news2025/1/18 10:47:57

目录

时间复杂度分析:

 递归

题1:爬楼梯

 解法1:递归

 解法2:循环

题2:两数之和

 解法1:暴力枚举

解法2: 哈希表

题3:合并两个有序数组 

解法1:直接合并后排序

解法2:双指针 

解法3:逆向双指针 

题4:移动零 

 解法1:双指针两次遍历

 解法2:双指针一次遍历 

题5:找出所有数组中消失的数字 

解法1:哈希表

解法2:原地修改

 题6:合并两个有序链表

 解法1:循环加双指针

解法2:递归 

题7:删除排序链表中的重复元素

解法一:一次遍历 

解法2:递归 

 题8:环形链表

解 :快慢指针

 题9:环形链表 II 

​编辑解法:快慢指针 

 题10:相交链表

 解法一:双指针

解法二:双指针plus

题11:反转链表

解法一:迭代

题12:回文链表

解法一:双指针

解法二:反转+快慢指针 

题13:链表的中间结点 

​编辑 解法一:两次遍历 

解法二:快慢指针 

题14:给定一个链表,删除链表中倒数第n个结点

​编辑解法一:两次遍历 

解法二:快慢指针


时间复杂度分析:

只关注循环执行次数最多的一段代码;

总复杂度等于最高阶项的复杂度;

嵌套代码的复杂度等于嵌套内外代码复杂度的乘积。

常见时间复杂度:

O(1)         常数阶

O(N)        线性阶

O(N^{2})         平方阶

O(\log N)        对数阶

O(N\log N)        线性对数阶

O(N^{3})        立方阶

O(2^{N})        指数阶

O(N!)        阶乘阶

 从小到大依次是:

O(1)   < O(\log N)  < O(N) < O(N\log N)  < O(N^{2})  < O(N^{3})  < O(2^{N})  < O(N!)   < O(N^{N})

 递归

1.一个问题的解可以分为几个子问题的解

2.这个问题与分解之后的子问题,除了问题规模不同,求解思路完全一样

3.存在基线/中止条件。

当满足以上3点是,就可以考虑使用递归来解决问题。

题1:爬楼梯

 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

 当n=1时,只有一种方法;当n=2时,有一次爬一阶和一次性爬两阶两种方法;

当n=3时,有三种方法,一次爬一阶、一阶两阶、两阶一阶;

可以发现,n阶台阶的方法,就是n-1阶与n-2阶方法的和。

 解法1:递归

n=1,n=2已知,这里假设n=6。

要计算n=6,就是计算n=5加n=4;

要计算n=5,就是计算n=3加n=4;

要计算n=4,就是计算n=3加n=2;

这里n=4,n=3会多次运算,会增大时间复杂度,

解决方法就是,定义一个HashMap,需要计算某个值时,先看看map里面有没有,如果有直接取,如果没有,在计算,然后存到map里面。

这样做,避免了重复运算,加快计算速度,减小时间复杂度。

class Solution {
    private HashMap<Integer,Integer> hashMap = new HashMap<>();
    public int climbStairs(int n) {
        if (n == 1)
            return 1;
        if (n == 2)
            return 2;
        if (hashMap.get(n) != null)
            return hashMap.get(n);
        else{
            int result = climbStairs(n-1) + climbStairs(n - 2);
            hashMap.put(n,result);
            return result;
        }
        
    }
}

 运行结果:

 解法2:循环

因为n=1和n=2很容易计算,我直接自底向上解决问题。

如图所示,f(1) + f(2) =f(3);

                  f(2) + f(3) =f(4);以此类推,只到求出f(n)。

public int climbStairs(int n) {
        if(n == 1) return 1;
        if(n == 2) return 2;
        int result = 0;
        //计算后面的值,就是前一个加前前一个
        //first保存前前一个的值
        //second保存前一个的值
        int first = 1;
        int second = 2;
        for(int i = 3; i <=n ; i++){
            result = first + second;
            first = second;
            second = result;
        }
        return result;
    }

题2:两数之和

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target  的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

你可以按任意顺序返回答案。

 解法1:暴力枚举

class Solution {
    public int[] twoSum(int[] nums, int target) {
        int[] arr = new int[2];
        for(int i = 0;i<nums.length;i++){
            for(int j = i+1;j<nums.length;j++){
                if(nums[i] + nums[j] == target){
                    arr[0] = i;
                    arr[1] = j;
                    break;
                }
            }
        }
        return arr;
    }
}

 

解法2: 哈希表

仔细观察暴力枚举法,就会发现,很多数重复参与比较。

如果不让那些数重复比较,就会减小时间复杂度。

方法也很简单,只要一个哈希表,当参加比较的数不符合条件时,将它存到哈希表中,这样只要一个for循环就ok,时间复杂度为O(n)。

class Solution {
    
    public int[] twoSum(int[] nums, int target) {
        Map<Integer,Integer> map = new HashMap<>();
        int n = nums.length;
        int[] arr = new int[2];
        for(int i = 0;i<=n;i++){
            int result = target - nums[i];
            Integer resultIndex = map.get(result);
            if(resultIndex!=null){
                arr[0] = resultIndex;
                arr[1] = i;
                break;
            }else{
                map.put(nums[i],i);
            }
        }
        return arr;
    }
}

题3:合并两个有序数组 

给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。

请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。

注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。

解法1:直接合并后排序

 从题目可以知道,num1的长度刚好可以放下num2数组。

所以,我们可以将num2直接放进num1的尾部,然后对整个数组进行排序。

public void merge(int[] nums1, int m, int[] nums2, int n) {
        for (int i = 0; i < n; i++) {
            nums1[m+i]=nums2[i];
        }
        Arrays.sort(nums1);
    }

效率很低,时间复杂度是O((m+n)log(m+n)) 

解法2:双指针 

 因为num1和num2已经是排序好的数组,我们可以利用这一点,从而省去排序的步骤,就可以减小时间复杂度。

利用双指针,指向两个数组的头部,比较两个元素的大小,每次将最小的元素取出,存进一个临时数组,最后将临时数组赋值给num1就ok了。

//双指针
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        int[] temp = new int[m+n];
        int p = 0;
        int q = 0;
        //每次比较的最小值
        int l = 0;
        while(p < m || q < n){
            if(p == m){
               l = nums2[q];
                q++;
            }else if(q == n){
                l = nums1[p];
                p++;
            }else if(nums1[p]<nums2[q]){
                l = nums1[p];
                p++;
            }else{
                l = nums2[q];
                q++;
            }
            temp[p+q-1] = l;
        }
        for(int i = 0;i<m+n;i++){
            nums1[i] = temp[i];
        }
    }

时间复杂度O(m+n),因为两个数组都循环了一次

空间复杂度:O(m+n)

解法3:逆向双指针 

解法2中空间复杂度 O(m+n),是因为需要一个长度为m+n的临时数组。

仔细观察题目,就会发现,num1后面有几个元素是0,也就是空,刚好可以存入num2的元素,利用这一点,优化双指针。

我们可以从两个数组的最后哟个元素开始遍历,将最大的元素存入num1中为0的位置即可。

//逆向双指针
    public void merge(int[] nums1, int m, int[] nums2, int n){
        int p = m-1;
        int q = n-1;
        int len = m+n-1;
        while(p>=0||q>=0){
            if(p < 0){
                nums1[len--] = nums2[q--];
            }else if(q < 0){
                nums1[len--] = nums1[p--];
            }else if(nums1[p] > nums2[q]){
                nums1[len--] = nums1[p--];
            }else{
                nums1[len--] = nums2[q--];
            }
        }
    }

时间复杂度为O(m+n),

空间复杂度为O(1),因为是在原地修改数组,不需要额外空间。 

题4:移动零 

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

请注意 ,必须在不复制数组的情况下原地对数组进行操作

 解法1:双指针两次遍历

定义两个指针,都指向数组头部元素,开始遍历数组。

如果第i个元素为0,i++;

如果第i个元素不为0,将第i个元素赋值给第j个元素,i++,j++;

直到最后一个元素。

最后将j之后的元素都赋值成0就ok了。

 public void moveZeroes(int[] nums) {
        if(nums==null) {
			return;
		}
        //双指针
        int i = 0;
        int j = 0;
        int len = nums.length;
        for(;i<len;i++){
            if(nums[i]!=0){
                nums[j++] = nums[i];
            }
        }
        for(;j<len;j++){
            nums[j] = 0;
        }
    }

时间复杂度:O(n) 

空间复杂度:O(1)

 解法2:双指针一次遍历 

 定义两个指针,开始时都指向数组的头部元素。

参考快速排序的思想:这里我们以0为中心点,把不等于0的放到左边,等于0的放到右边。

从头遍历,只要i处元素不等于0,就交换j处与i处的元素。

 public void moveZeroes(int[] nums) {
        if(nums==null) {
			return;
		}
        //双指针一次遍历
        int i = 0;
        int j = 0;
        int len = nums.length;
        for(;i<len;i++){
            if(nums[i]!=0){
                int temp = nums[i];
                nums[i] = nums[j];
                nums[j++] = temp;
            }
        }
    }

时间复杂度:O(n)

空间复杂度:O(1) 

题5:找出所有数组中消失的数字 

 给你一个含 n 个整数的数组 nums ,其中 nums[i] 在区间 [1, n] 内。请你找出所有在 [1, n] 范围内但没有出现在 nums 中的数字,并以数组的形式返回结果。

示例 1:

输入:nums = [4,3,2,7,8,2,3,1]
输出:[5,6]

示例 2:

输入:nums = [1,1]
输出:[2]

进阶:你能在不使用额外空间且时间复杂度为 O(n) 的情况下解决这个问题吗? 你可以假定返回的数组不算在额外空间内。 

解法1:哈希表

我们可以使用哈希表,将数组的元素都存储到哈希表中,然后遍历数组,判断元素是否出现在哈希集合中。

public List<Integer> findDisappearedNumbers(int[] nums) {
        Set<Integer> map = new HashSet<Integer>();
        List<Integer> list = new ArrayList<>();
        for (int num : nums) {
            map.add(num);
        }

        for (int i = 1;i<=nums.length;i++){
            if(!map.contains(i)){
                list.add(i);
            }
        }
        return list;
    }

时间复杂度:O(n) 

空间复杂度:O(n)

 不满足要求!!!

解法2:原地修改

 题目意思就是不让用哈希表,那我们可以模仿哈希表,在原地修改。

仔细看题,数组长度为n,数组中元素刚好在[1,n]这个范围内。

这样做,遍历数组,每遇到一个数x,我们就让nums[x-1]增加n或将其改变成对应的负数,

遍历一遍后,这个数组中不大于n或者不是负数的数的下标加1就是那个消失的数字。

注意:由于nums[x]处的元素可能被修改过,我们需要对其还原。

(nums[x] - 1)%n

public List<Integer> findDisappearedNumbers(int[] nums) {
        int n = nums.length;
        List<Integer> list = new ArrayList<>();
        for(int num:nums){
            //还原它本来的值
            int x = (num - 1) % n;
            nums[x] += n;
        }
        for(int i = 0; i < n; i++){
            if(nums[i] <= n){
                list.add(i + 1);
            }
        }
        return list;
    }

时间复杂度:O(n) 

空间复杂度:O(1)

 题6:合并两个有序链表

 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

示例 1:

输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]

示例 2:

输入:l1 = [], l2 = []
输出:[]

示例 3:

输入:l1 = [], l2 = [0]
输出:[0]

提示:

  • 两个链表的节点数目范围是 [0, 50]
  • -100 <= Node.val <= 100
  • l1 和 l2 均按 非递减顺序 排列

 解法1:循环加双指针

 给的链表递增排列。

定义一个节点p,定义两个指针分别指向两个链表的头节点,比较大小,第一次,谁小就把谁接到p节点后面,对应的指针向后挪一位,继续比较,依次往后面接,直到有一个指针指向空。

这个时候,将剩下的那个链表直接接到另一个链表后面。

/**
 * Definition for singly-linked list.
 * public 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 mergeTwoLists(ListNode list1, ListNode list2) {
        ListNode p = new ListNode(0);
        ListNode pre = p;
        if(list1 == null) return list2;
        if(list2 == null) return list1;
        while(list1 != null && list2!= null){
            if(list1.val < list2.val){
                pre.next = list1;
                list1 = list1.next;
            }else{
                pre.next = list2;
                list2 = list2.next;
            }
            
            pre = pre.next;
        }
        if(list1 == null){
            pre.next = list2;
        }
         if(list2 == null){
            pre.next = list1;
        }
        return p.next;
    }
}

 时间复杂度:O(m+n)

空间复杂度:O(1)

解法2:递归 

 递归就是自己调用自己,而且要有终止条件。

先判断头结点,谁小谁就指向其余结点的合并结果,这里的终止条件就是某个链表为空,也就是说链表已经合并完成。

递归这玩意给人的感觉就是只可意会不可言传

public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        if(list1 == null) return list2;
        if(list2 == null) return list1;
        if(list1.val < list2.val){
            list1.next = mergeTwoLists(list1.next,list2);
            return list1;
        }else{
            list2.next = mergeTwoLists(list2.next,list1);
            return list2;
        }
    }

 时间复杂度:O(m+n),

空间复杂度:O(m+n) 

题7:删除排序链表中的重复元素

给定一个已排序的链表的头 head , 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 。

解法一:一次遍历 

 从链表头结点开始,依次向后遍历,判断当前结点的值是否等于下一个结点的值,如果相等,让当前结点直接指向下下一个结点,直到遍历完整个链表。

public ListNode deleteDuplicates(ListNode head) {
       if(head == null){
           return head;
       }

       ListNode h = head;
       while(h.next != null){
           if(h.val == h.next.val){
               h.next = h.next.next;
           }else{
               h = h.next;
           }
       }
       return head;
    }

 时间复杂度:O(n) 

空间复杂度:O(1)

解法2:递归 

 当处理完一个结点后,剩下的结点处理方式与第一个相同。

public ListNode deleteDuplicates(ListNode head) {
        if(head == null || head.next == null) return head;
        head.next = deleteDuplicates(head.next);
        return head.val == head.next.val ? head.next : head;
    }

时间复杂度:O(n)

空间复杂度:O(n) 

 题8:环形链表

给你一个链表的头节点 head ,判断链表中是否有环。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。

如果链表中存在环 ,则返回 true 。 否则,返回 false 。

   

解 :快慢指针

我们定义两个指针,一个快指针,一个慢指针。然后让指针往后遍历链表,如果两个指针相遇了,就说明有环。

 public boolean hasCycle(ListNode head) {
        if(head == null) return false;

        ListNode fast = head;
        ListNode slow = head;
        while(fast.next != null && fast.next.next != null){
            fast = fast.next.next;
            slow = slow.next;
            if(fast == slow){
                return true;
            }
        }
        return false;
    }

  

时间复杂度:O(n)

空间复杂度:O(1) 

 题9:环形链表 II 

给定一个链表的头节点  head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

不允许修改 链表。

解法:快慢指针 

 上一题让我们确定环是否存在,我们定义两个快慢指针,如果相遇,则说明环存在。

现在,让我们确定入环的第一个结点,思路跟上一题差不多,只不过多加了一个判断。

首先,利用上一题的解题思路判断环是否存在,如果存在,我们让慢指针指向头结点,然后让快慢指针的速度相同,如果它两再次相遇,就找到了入环时的第一个结点。

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode detectCycle(ListNode head) {
        if(head == null) return null;

        ListNode fast = head;
        ListNode slow = head;
        boolean flag = false;
        while(fast.next != null && fast.next.next!=null){
            fast = fast.next.next;
            slow = slow.next;
            if(fast == slow){
                flag = true;
                break;
            }
        }
        //环存在
        if(flag){
            //慢指针指向头结点
            slow = head;
            while(slow != fast){
                //慢指针和快指针移动速度一样
                slow = slow.next;
                fast = fast.next;

            }
            return slow;
        }
        return null;
    }
}

 时间复杂度:O(n)

空间复杂度:O(1) 

 题10:相交链表

 给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。

题目数据 保证 整个链式结构中不存在环。

注意,函数返回结果后,链表必须 保持其原始结构 。

自定义评测:

评测系统 的输入如下(你设计的程序 不适用 此输入):

  • intersectVal - 相交的起始节点的值。如果不存在相交节点,这一值为 0
  • listA - 第一个链表
  • listB - 第二个链表
  • skipA - 在 listA 中(从头节点开始)跳到交叉节点的节点数
  • skipB - 在 listB 中(从头节点开始)跳到交叉节点的节点数

评测系统将根据这些输入创建链式数据结构,并将两个头节点 headA 和 headB 传递给你的程序。如果程序能够正确返回相交节点,那么你的解决方案将被 视作正确答案 。

 

 解法一:双指针

 定义两个指针,都依次往后遍历链表,如果两个链表长度相同,那么当两个指针指向同一个结点时,就是他们的相交的起始结点。

如果长度不相同,当短的那条链表指向null时,让他重新指向另外一条链表的头结点,继续遍历,按照这个思路,两个指针总会相遇,相遇即找到相交的起始结点。

public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if(headA == null || headB == null){ return null;}
        ListNode a = headA;
        ListNode b = headB;

        while(a != b){
            if(a == null){
                a = headB;
            }else{
                a = a.next;
            }
            if(b == null){
                b = headA;
            }else{
                b = b.next;
            }
        }
        return a;
    }

 时间复杂度:O(m+n) 

空间复杂度:O(1)

解法二:双指针plus

 我们首先遍历A链表,遍历B链表得到两个链表的长度,然后相减得到差值d。

接下来,我们让长的那个链表先移动d个结点,这是两个链表的剩余长度就相同,当两个指针指向同一个结点时,就找到了相交的起始链表。

 public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if(headA == null || headB == null){ return null;}
        ListNode a = headA;
        ListNode b = headB;
        int alen = 0;
        int blen = 0;
        int d = 0;
        while(a != null){
            a = a.next;
            alen+=1;
        }
        while(b != null){
            b = b.next;
            blen += 1;
        }
        if(alen>blen){
            a = headA;
            b = headB;
            d = alen - blen;
        }else{
            a = headB;
            b = headA;
            d = blen - alen;
        }
        for(int i = 0;i<d;i++){
            a = a.next;
        }
        while(a!=null&&b!=null){
            if(a == b){
                return a;
            }
            a = a.next;
            b = b.next;
        }
    return null;
    }

 时间复杂度:O(m+n) 

空间复杂度:O(1)

题11:反转链表

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

 

进阶:链表可以选用迭代或递归方式完成反转。你能否用两种方法解决这道题?

解法一:迭代

我们要反转链表,就是让链表第一个结点成为最后一个结点,最后一个结点成为第一个结点。

从头开始遍历链表,让当前结点指向上一个结点,因为头结点的没有前一个结点,所以我们要定义一个preNode=null存储前一个结点,还要存储后一个结点。

public ListNode reverseList(ListNode head) {
        ListNode preNode = null;
        ListNode curr = head;
        while(curr!=null){
            ListNode next = curr.next;
            curr.next = preNode;
            preNode = curr;
            curr = next;
        }
        return preNode;
    }

 时间复杂度:O(n)

空间复杂度:O(1)

题12:回文链表

 给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 

进阶:你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?

解法一:双指针

将链表的元素赋值到数组中,然后利用双指针判断是否回文。

/**
 * Definition for singly-linked list.
 * public 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 boolean isPalindrome(ListNode head) {
        List<Integer> list = new ArrayList<>();

        ListNode curr = head;
        while(curr != null){
            list.add(curr.val);
            curr = curr.next;
        }
        int len = list.size();
        int left = 0;
        int right = len - 1;
        while(left < right){
            if(list.get(left).equals(list.get(right))){
                left++;
                right--;
            }else{
                return false;
            }
        }
        return true;
    }
}

 时间复杂度:O(n)

空间复杂度:O(n)

解法二:反转+快慢指针 

题目进阶要求,空间复杂度为O(1)。

所以我们直接在链表内部判断是否回文。

我们先利用快慢指针找到链表中心点,然后将中心点之后的元素反转,在判断前部分元素和后半部分元素的反转结果是否相同就ok了。

/**
 * Definition for singly-linked list.
 * public 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 boolean isPalindrome(ListNode head) {
        ListNode fast = head;
        ListNode slow = head;
        while(fast != null && fast.next != null){
            fast = fast.next.next;
            slow = slow.next;
        }

        if(fast != null){
            slow = slow.next;
        }
        slow = reverse(slow);
        fast = head;
        while(slow != null){
            if(slow.val != fast.val){
                return false;
            }
            slow = slow.next;
            fast = fast.next;
        }
        return true;

    }
    //反转链表
    public ListNode reverse(ListNode head){
        ListNode pre = null;
        ListNode curr = head;
        while(curr != null){
            ListNode next = curr.next;
            curr.next = pre;
            pre = curr;
            curr = next;
        }
        return pre;
    }
}

 时间复杂度:O(n)

空间复杂度:O(1)

题13:链表的中间结点 

给你单链表的头结点 head ,请你找出并返回链表的中间结点。

如果有两个中间结点,则返回第二个中间结点。

 解法一:两次遍历 

第一次遍历得到链表的长度,除以2 +1,就是中间结点所在的位置,第二次遍历就可以找到中间结点。

/**
 * Definition for singly-linked list.
 * public 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 middleNode(ListNode head) {
        ListNode h = head;
        int len = 0;
        while(h != null){
            h = h.next;
            len++;
        }
        h = head;
        int size = (len / 2) ;
        while(size > 0){
            h = h.next;
            size--;
        }
        return h;
    }
}

 时间复杂度:O(n)

空间复杂度:O(1)

解法二:快慢指针 

 定义两个指针,一个快,一个慢,当快指针指向最后一个结点或者null时,慢指针指向的就是中间结点。

/**
 * Definition for singly-linked list.
 * public 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 middleNode(ListNode head) {
        ListNode fast = head;
        ListNode slow = head;
        while(fast != null && fast.next != null){
            fast = fast.next.next;
            slow = slow.next;
        }
        return slow;
    }
}

 

时间复杂度:O(n)

空间复杂度:O(1)

题14:给定一个链表,删除链表中倒数第n个结点

给定一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。 

进阶:能尝试使用一趟扫描实现吗?



解法一:两次遍历 

第一次遍历得到链表的长度k,要求删除倒数第n个结点,反过来,删除第k-n+1个结点。

第二次遍历删除。

/**
 * Definition for singly-linked list.
 * public 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 removeNthFromEnd(ListNode head, int n) {
        ListNode dummy = new ListNode(0,head);
        ListNode cur = dummy;
        int k = 0;
        ListNode h = head;
        while(h != null){
            h = h.next;
            k++;
        }
        int len = k - n + 1;
        for(int i = 1;i<len;i++){
            cur = cur.next;
        }
       
        cur.next = cur.next.next;
        ListNode result = dummy.next;
        return result;

    }
}

 

时间复杂度:O(n)

空间复杂度:O(1)

解法二:快慢指针

 题目进阶要求,一次遍历。

定义两个指针,快指针先移动n-1次,然后两个指针同时移动,当快指针移动到最后一个结点时,慢指针所在的结点就是要删除的结点。

/**
 * Definition for singly-linked list.
 * public 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 removeNthFromEnd(ListNode head, int n) {
        ListNode dummy = new ListNode(0,head);
        ListNode fast = head;
        ListNode slow = dummy;
        while(n>0){
            fast = fast.next;
            n--;
        }
        while(fast!=null){
            fast = fast.next;
            slow = slow.next;
        }

        slow.next = slow.next.next;
        ListNode result = dummy.next;
        return result;

    }
}

 

时间复杂度:O(n)

空间复杂度:O(1)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1416476.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

华为机考入门python3--(0)模拟题3-计算字符串重新排列数

分类&#xff1a;排列组合 知识点&#xff1a; 计算字符串中每个字符出现的次数 Counter(string) 计算列表中每个元素出现的次数 Counter(list) 阶乘 math.factorial(num) 排列去重 题目来自【华为招聘模拟考试】 先把每个字符当成唯一出现过一次&#xff0c;计算所有排列…

Linux文本三剑客---awk经典案例

awk&#xff08;是一种处理文本文件的应用程序&#xff0c;它依次处理文件的每一行&#xff0c;并读取里面的每一个字段。&#xff09; awk 包含几个特殊的内建变量&#xff08;可直接用&#xff09;如下所示&#xff1a; 1、获取根分区剩余大小 #可以使用df -h命令来查看所有…

详解顺序结构双指针处理算法

&#x1f380;个人主页&#xff1a; https://zhangxiaoshu.blog.csdn.net &#x1f4e2;欢迎大家&#xff1a;关注&#x1f50d;点赞&#x1f44d;评论&#x1f4dd;收藏⭐️&#xff0c;如有错误敬请指正! &#x1f495;未来很长&#xff0c;值得我们全力奔赴更美好的生活&…

如何在Ubuntu安装配置SVN服务端并实现无公网ip访问内网资料库

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” 文章目录 前言1. Ubuntu安装SVN服务2. 修改配置文件2.1 修改svnserve.conf文件2.2 修改passwd文件2.3 修改au…

swift - reduce简介

reduce 减少&#xff0c;降低&#xff1b;&#xff08;烹调中&#xff09;使变浓稠&#xff0c;收汁&#xff1b;<美>节食减肥&#xff1b;使沦为&#xff0c;使陷入&#xff08;不好的境地&#xff09;&#xff1b;迫使&#xff0c;使不得不&#xff08;做&#xff09;&…

【C++】输入输出、缺省参数、函数重载

目录 C的输入和输出 缺省参数 概念 缺省参数的分类 全缺省参数 半缺省参数 函数重载 概念 C支持函数重载的原理--名字修饰 C的输入和输出 #include<iostream> // std是C标准库的命名空间名&#xff0c;C将标准库的定义实现都放到这个命名空间中 using namespace …

BKP备份寄存器、RTC实时时钟

目录 1. BKP (Backup Registers)备份寄存器 2. RTC (Real Time Clock)实时时钟 1. BKP (Backup Registers)备份寄存器 BKP可用于存储用户应用程序数据。当VDD (2.0-3.6V)电源被切断,他们仍然由VBAT (1.8-3.6V)维持供电。当系统在待机模式下被唤醒&#xff0c;或系统复位或…

【大数据】Flink 架构(一):系统架构

Flink 架构&#xff08;一&#xff09;&#xff1a;系统架构 1.Flink 组件1.1 JobManager1.2 ResourceManager1.3 TaskManager1.4 Dispatcher 2.应用部署2.1 框架模式2.2 库模式 3.任务执行4.高可用设置4.1 TaskManager 故障4.2 JobManager 故障 Flink 是一个用于状态化并行流处…

BeanUtils和BeanCopier性能复制Bean工具比较

文章目录 一、前言二、实验三、原理1、BeanUtils2、BeanCopier 四、总结 一、前言 我们本篇比较的是复制Bean对象的工具&#xff0c;分别是org.springframework.beans.BeanUtils和 net.sf.cglib.beans.BeanCopier 二、实验 import net.sf.cglib.beans.BeanCopier; import org…

部署LNMP、Nginx+FastCGI、Nginx地址重写语法,地址重写应用案例

1 案例1&#xff1a;部署LNMP环境 1.1 问题 安装部署LNMP环境实现动态网站解析 静态网站 在不同环境下访问&#xff0c;网站内容不会变化 动态网站 在不同环境下访问&#xff0c;网站内容有可能发生变化 安装部署Nginx、MariaDB、PHP、PHP-FPM&#xff1b;启动Nginx、Mari…

java—AWT

AWT 课程&#xff1a;1、GUI编程简介_哔哩哔哩_bilibili 一.介绍 包含了很多类和接口&#xff01;GUI&#xff01;元素&#xff1a;窗口、按钮、文本框java.awt 二.窗口 1.构造 2.方法 // 实例化frame类Frame frame new Frame("这个一个框");// 设置可见性frame.…

游戏设计模式

单列模式 概念 单例模式是一种创建型设计模式&#xff0c;可以保证一个类只有一个实例&#xff0c;并提供一个访问该实例的全局节点。 优点 可以派生&#xff1a;在单例类的实例构造函数中可以设置以允许子类派生。受控访问&#xff1a;因为单例类封装他的唯一实例&#xf…

Cyberdog2 docker环境软件源无法被验证问题

搭建docker系统后更新软件源sudo apt-get update出现异常 经过查询GPT&#xff0c;使用如下方式成功解决 从keyserver.ubuntu.com获取缺失的公钥&#xff0c;并添加到apt-key中。具体命令如下&#xff1a; gpg --keyserver keyserver.ubuntu.com --recv-keys F42ED6FBAB17C6…

C++的关键字,命名空间,缺省参数,函数重载以及原理

文章目录 前言一、C关键字(C98)二、命名空间命名空间介绍命名空间的使用 三、C输入【cin】& 输出【cout】四、缺省参数缺省参数概念缺省参数分类缺省参数的使用小结一下 五、函数重载函数重载介绍函数重载类型 六、C支持函数重载的原理--名字修饰(name Mangling)【重点】 前…

【开源】基于JAVA语言的智慧社区业务综合平台

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 业务类型模块2.2 基础业务模块2.3 预约业务模块2.4 反馈管理模块2.5 社区新闻模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 业务类型表3.2.2 基础业务表3.2.3 预约业务表3.2.4 反馈表3.2.5 社区新闻表 四、系统展…

[BUUCTF]-PWN:hitcon2014_stkof解析

又是一道堆题&#xff0c;先看保护 关键信息&#xff0c;64位&#xff0c;没开pie。再看ida 大致就是alloc创建堆块&#xff0c;free释放堆块&#xff0c;fill填充堆块内容&#xff0c;以及一个看起来没啥用的函数&#xff0c;当然我也没利用这个函数去解题 这里有两种解法 解…

Python tkinter (6) Listbox

Python的标准Tk GUI工具包的接口 tkinter系列文章 python tkinter窗口简单实现 Python tkinter (1) —— Label标签 Python tkinter (2) —— Button标签 Python tkinter (3) —— Entry标签 Python tkinter (4) —— Text控件 GUI 目录 Listbox 创建listbox 添加元素…

Java版大厂算法题1——数字颠倒

问题描述 输入一个整数&#xff0c;将这个整数以字符串的形式逆序输出&#xff0c;程序不考虑负数的情况&#xff0c;若数字含有0&#xff0c;则逆序形式也含有0。如果输入为100&#xff0c;则输出为001。 数据范围&#xff1a;0<n<(2^30)-1 * 输入描述&#xff1a;输入…

2023启示录|虚拟人这一年

图片&#xff5c;《银翼杀手 2049》剧照 作者丨程心 编辑丨罗辑 2023 年&#xff0c;大模型 “救活” 了很多行业&#xff0c;其中最为反转的&#xff0c;就是把虚拟数字人&#xff08;以下简称虚拟人&#xff09;从活死人墓里拉了出来。 还没开年&#xff0c;在 2022 年火…

保姆级教学:Java项目从0到1部署到云服务器

目录 1、明确内容 2、apt 2.1、apt 语法 2.2、常用命令 2.3、更新apt 3、安装JDK17 4、安装MySQL 4.1、安装 4.2、检查版本及安装位置 4.3、初始化MySQL配置⭐ 4.4、检查状态 4.5、配置远程访问⭐ 4.6、登录MySQL 4.7、测试数据库 4.8、设置权限与密码⭐ 5、安…