【每日三题】2022-3-29 LeetCode 25-27

25.K个一组翻转链表

思路:常规情况,使用栈遍历链表,每当栈中的元素等于K时,则翻转一次。因为栈是后进先出的,出栈顺序恰好与入栈顺序相反,可以轻易实现翻转。但这种思路,需要K的空间来存储链表结点,题目提出挑战使用常数空间来翻转,因此挑战使用常数空间。可以观察到每一次翻转,需要保存的主要有这个K元组的前一个结点和后一个结点,因此使用四个结点指针,其中一个保存前一个结点,另一个保存后一个结点,第三个指针和第四个指针用来在中间的k元组进行翻转用。以链表{1,2,3,4,5}, k = 3为例子,步骤如下:
在这里插入图片描述

  1. 首先添加VirtualHead,用来保留结果返回,并且用来辅助第一次翻转。保存前一个结点。
    在这里插入图片描述
  2. 将head指针推进k-1用来寻找后一个结点,若寻找过程中遇到结尾,则说明剩余的结点不够翻转了,直接返回。

在这里插入图片描述

  1. 此时K元组为{1,2,3},其中第一个结点即为下一个K元组的前一个结点。同时将前一个结点的next指向此时K元组的最后一个结点,因为翻转过后,最后一个结点会变为第一个结点。
    在这里插入图片描述

  2. 将此时newHead的下一个结点指向tail,因为翻转完成后,newHead会作为K元组的最后一个结点。更改前记得保留newHead原本的next结点,即2结点。
    在这里插入图片描述

  3. 此时k元组与前一个结点和后一个结点的关系已经转换完成,接下来只需遍历k元组并将其之间的关系翻转。这里需要用三个指针用来翻转,分别保留当前结点的前一个结点,当前结点以及当前结点的后一个结点,以当前结点为2为例。
    在这里插入图片描述

  4. 使2指向1,同时递进当前结点为3,更新相关指针。
    在这里插入图片描述

  5. 翻转3后,此时当前结点指向tail,表明k元组翻转完成。翻转后的结果如图。
    在这里插入图片描述

  6. 此时,在向前递进head的时候,提前遇到了链表末尾,则表明当前已不存在k元组可以翻转,直接返回结果。

代码:

 public ListNode reverseKGroup(ListNode head, int k) {
     ListNode virtualHead = new ListNode(-1), ptr = virtualHead, newHead = ptr, tail = ptr;
     virtualHead.next = head;
     while (head != null) {
         for (int i = 0; i < k - 1; i++) {
             if (head == null) {
                 break;
             }
             head = head.next;
         }
         if (head == null) {
             break;
         }
         tail = head.next;
         ptr = newHead;
         newHead = newHead.next;
         ptr.next = head;
         head = newHead.next;
         newHead.next = tail;
         ptr = newHead;
         while (head != tail) {
             ListNode next = head.next;
             head.next = ptr;
             ptr = head;
             head = next;
         }
     }
     return virtualHead.next;
 }

时间复杂度:O(n)

空间复杂度:O(1)

26.删除有序数组中的重复项

思路:双指针,一个写指针记录上一次写入的位置,一个读指针遍历数组。因为第一个数总是不重复,因此写指针指向第一个数代表已经将其写入,而读指针从第二个数开始遍历。当读指针指向的值等于上次写指针指向的值时,说明当前读到的是重复值,读指针向前推进一步。当不相等时,首先让写指针推进1指向现在需要写入的位置,写入当前值,然后读指针继续向后遍历。当读指针遍历完成后,返回写指针的位置。

代码:

public int removeDuplicates(int[] nums) {
    if (nums.length <= 1) {
        return nums.length;
    }
    int writePtr = 0, readPtr = 1;
    while (readPtr < nums.length) {
        if (nums[readPtr] == nums[writePtr]) {
            readPtr ++;
        } else {
            writePtr ++;
            nums[writePtr] = nums[readPtr];
            readPtr ++;
        }
    }
    return writePtr + 1;
}

时间复杂度:O(n)

空间复杂度:O(1)

27.移除元素

思路:与前一道题类似,需要注意的是两个指针的起始位置。此时第一个数可能是需要移除的元素,因此写指针为-1代表还没有写入任何元素,而读指针则从第一个元素开始遍历。

代码:

public int removeElement(int[] nums, int val) {
    if (nums.length == 0) {
        return 0;
    }
    int writePtr = -1, readPtr = 0;
    while (readPtr < nums.length) {
        if (nums[readPtr] == val) {
            readPtr ++;
        } else {
            writePtr ++;
            nums[writePtr] = nums[readPtr];
            readPtr ++;
        }
    }
    return writePtr + 1;
}

时间复杂度:O(n)

空间复杂度:O(1)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值