• Leetcode——链表和数组(1)


    删除排序数组中的重复项

    给定一个排序数组,你需要在 原地 删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。

    不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。

    示例 1:

    给定数组 nums = [1,1,2], 
    
    函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。 
    
    你不需要考虑数组中超出新长度后面的元素。
    

    示例 2:

    给定 nums = [0,0,1,1,1,2,2,3,3,4],
    
    函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。
    
    你不需要考虑数组中超出新长度后面的元素。
    

    说明:

    为什么返回数值是整数,但输出的答案是数组呢?

    请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。

    你可以想象内部操作如下:

    // nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝
    int len = removeDuplicates(nums);
    
    // 在函数里修改输入数组对于调用者是可见的。
    // 根据你的函数返回的长度, 它会打印出数组中该长度范围内的所有元素。
    for (int i = 0; i < len; i++) {
        print(nums[i]);
    }
    

    双指针

    使用快慢指针来记录遍历的坐标,最开始时两个指针都指向第一个数字

    如果两个指针指的数字相同,则快指针向前走一步

    如果不同,则两个指针都向前走一步,这样当快指针走完整个数组后,慢指针当前的坐标加1就是数组中不同数字的个数

    class Solution {
    public:
        int removeDuplicates(vector<int>& nums) {
            int pre = 0, cur = 0, n = nums.size();
            while (cur < n) {
                if (nums[pre] == nums[cur]) ++cur;
                else nums[++pre] = nums[cur++];
            }
            return nums.empty() ? 0 : (pre + 1);
        }
    };
    

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

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

    示例 1:
    
    输入: 1->1->2
    输出: 1->2
    示例 2:
    
    输入: 1->1->2->3->3
    输出: 1->2->3
    

    遍历

    遍历链表,每个结点和其后面的结点比较,

    如果结点值相同了,只要将前面结点的 next 指针跳过紧挨着的相同值的结点,指向后面一个结点。

    这样遍历下来,所有重复的结点都会被跳过,留下的链表就是没有重复项的了

    class Solution {
    public:
        ListNode* deleteDuplicates(ListNode* head) {
            ListNode *cur = head;
            while (cur && cur->next) {
                if (cur->val == cur->next->val) {
                    cur->next = cur->next->next;
                } else {
                    cur = cur->next;
                }
            }
            return head;
        }
    };
    

    递归

    首先判断是否至少有两个结点,

    若不是的话,直接返回 head

    否则对 head->next 调用递归函数,并赋值给 head->next

    返回的时候,head 结点先跟其身后的结点进行比较,

    如果值相同,那么返回后面的一个结点,当前的 head 结点就被跳过了,

    而如果不同的话,还是返回 head 结点。

    class Solution {
    public:
        ListNode* deleteDuplicates(ListNode* head) {
            if (!head || !head->next) return head;
            head->next = deleteDuplicates(head->next);
            return (head->val == head->next->val) ? head->next : head;
        }
    };
    

    环形链表

    给定一个链表,判断链表中是否有环。

    为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos-1,则在该链表中没有环。

    示例 1:

    输入:head = [3,2,0,-4], pos = 1
    输出:true
    解释:链表中有一个环,其尾部连接到第二个节点。
    

    img

    示例 2:

    输入:head = [1,2], pos = 0
    输出:true
    解释:链表中有一个环,其尾部连接到第一个节点。
    

    img

    示例 3:

    输入:head = [1], pos = -1
    输出:false
    解释:链表中没有环。
    

    img

    进阶:

    你能用 O(1)(即,常量)内存解决此问题吗?

    双指针

    设两个指针,一个每次走一步的慢指针和一个每次走两步的快指针,如果链表里有环的话,两个指针最终肯定会相遇。

    class Solution {
    public:
        bool hasCycle(ListNode *head) {
            ListNode *slow = head, *fast = head;
            while (fast && fast->next) {
                slow = slow->next;
                fast = fast->next->next;
                if (slow == fast) return true;
            }
            return false;
        }
    };
    

    环形链表 II

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

    为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos-1,则在该链表中没有环。

    说明:不允许修改给定的链表。

    示例 1:

    输入:head = [3,2,0,-4], pos = 1
    输出:tail connects to node index 1
    解释:链表中有一个环,其尾部连接到第二个节点。
    

    img

    示例 2:

    输入:head = [1,2], pos = 0
    输出:tail connects to node index 0
    解释:链表中有一个环,其尾部连接到第一个节点。
    

    img

    示例 3:

    输入:head = [1], pos = -1
    输出:no cycle
    解释:链表中没有环。
    

    img

    进阶:
    你是否可以不用额外空间解决此题?

    双指针

    快指针每次走2,慢指针每次走1,快指针走的距离是慢指针的两倍。

    而快指针又比慢指针多走了一圈。所以 head 到环的起点+环的起点到他们相遇的点的距离 与 环一圈的距离相等。

    现在重新开始,head 运行到环起点 和 相遇点到环起点 的距离也是相等的,相当于他们同时减掉了 环的起点到他们相遇的点的距离

    class Solution {
    public:
        ListNode *detectCycle(ListNode *head) {
            ListNode *slow = head, *fast = head;
            while (fast && fast->next) {
                slow = slow->next;
                fast = fast->next->next;
                if (slow == fast) break;
            }
            if (!fast || !fast->next) return NULL;
            slow = head;
            while (slow != fast) {
                slow = slow->next;
                fast = fast->next;
            }
            return fast;
        }
    };
    

    反转链表

    反转一个单链表。

    示例:

    输入: 1->2->3->4->5->NULL
    输出: 5->4->3->2->1->NULL
    

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

    遍历

    在原链表之前建立一个空的newHead,因为首节点会变,然后从head开始,将之后的一个节点移到newHead之后,重复此操作直到head成为末节点为止

    C++

    class Solution {
    public:
        ListNode* reverseList(ListNode* head) {
            ListNode *newHead = NULL;
            while (head) {
                ListNode *t = head->next;
                head->next = newHead;
                newHead = head;
                head = t;
            }
            return newHead;
        }
    };
    

    Java

    class Solution {
    	public ListNode reverseList(ListNode head) {
    		//申请节点,pre和 cur,pre指向null
    		ListNode pre = null;
    		ListNode cur = head;
    		ListNode tmp = null;
    		while(cur!=null) {
    			//记录当前节点的下一个节点
    			tmp = cur.next;
    			//然后将当前节点指向pre
    			cur.next = pre;
    			//pre和cur节点都前进一位
    			pre = cur;
    			cur = tmp;
    		}
    		return pre;
    	}
    }
    

    递归

    不断的进入递归函数,直到head指向倒数第二个节点,

    因为head指向空或者是最后一个结点都直接返回了,

    newHead则指向对head的下一个结点调用递归函数返回的头结点,

    此时newHead指向最后一个结点,

    然后head的下一个结点的next指向head本身,

    这个相当于把head结点移动到末尾的操作,

    因为是回溯的操作,所以head的下一个结点总是在上一轮被移动到末尾了,

    head之后的next还没有断开,所以可以顺势将head移动到末尾,

    再把next断开,最后返回newHead即可

    class Solution {
    public:
        ListNode* reverseList(ListNode* head) {
            if (!head || !head->next) return head;
            ListNode *newHead = reverseList(head->next);
            head->next->next = head;
            head->next = NULL;
            return newHead;
        }
    };
    

    反转链表 II

    反转从位置 mn 的链表。请使用一趟扫描完成反转。

    说明:
    1 ≤ mn ≤ 链表长度。

    示例:

    输入: 1->2->3->4->5->NULL, m = 2, n = 4
    输出: 1->4->3->2->5->NULL
    

    遍历

    class Solution {
    public:
        ListNode *reverseBetween(ListNode *head, int m, int n) {
            ListNode *dummy = new ListNode(-1), *pre = dummy;
            dummy->next = head;
            for (int i = 0; i < m - 1; ++i) pre = pre->next;
            ListNode *cur = pre->next;
            for (int i = m; i < n; ++i) {
                ListNode *t = cur->next;
                cur->next = t->next;
                t->next = pre->next;
                pre->next = t;
            }
            return dummy->next;
        }
    };
    

    K 个一组翻转链表

    给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。

    k 是一个正整数,它的值小于或等于链表的长度。

    如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

    示例:

    给你这个链表:1->2->3->4->5

    k = 2 时,应当返回: 2->1->4->3->5

    k = 3 时,应当返回: 3->2->1->4->5

    说明:

    • 你的算法只能使用常数的额外空间。
    • 你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。

    遍历_1

    两个函数,一个是用来分段的,一个是用来翻转的

    class Solution {
    public:
        ListNode* reverseKGroup(ListNode* head, int k) {
            if (!head || k == 1) return head;
            ListNode *dummy = new ListNode(-1), *pre = dummy, *cur = head;
            dummy->next = head;
            for (int i = 1; cur; ++i) {
                if (i % k == 0) {
                    pre = reverseOneGroup(pre, cur->next);
                    cur = pre->next;
                } else {
                    cur = cur->next;
                }
            }
            return dummy->next;
        }
        ListNode* reverseOneGroup(ListNode* pre, ListNode* next) {
            ListNode *last = pre->next, *cur = last->next;
            while(cur != next) {
                last->next = cur->next;
                cur->next = pre->next;
                pre->next = cur;
                cur = last->next;
            }
            return last;
        }
    };
    

    遍历_2

    一个函数中完成,首先遍历整个链表,统计出链表的长度,然后如果长度大于等于k

    class Solution {
    public:
        ListNode* reverseKGroup(ListNode* head, int k) {
            ListNode *dummy = new ListNode(-1), *pre = dummy, *cur = pre;
            dummy->next = head;
            int num = 0;
            while (cur = cur->next) ++num;
            while (num >= k) {
                cur = pre->next;
                for (int i = 1; i < k; ++i) {
                    ListNode *t = cur->next;
                    cur->next = t->next;
                    t->next = pre->next;
                    pre->next = t;
                }
                pre = cur;
                num -= k;
            }
            return dummy->next;
        }
    };
    

    递归

    用 head 记录每段的开始位置,cur 记录结束位置的下一个节点,

    然后调用 reverse 函数来将这段翻转,然后得到一个 new_head,原来的 head 就变成了末尾,

    这时候后面接上递归调用下一段得到的新节点,返回 new_head 即可

    class Solution {
    public:
        ListNode* reverseKGroup(ListNode* head, int k) {
            ListNode *cur = head;
            for (int i = 0; i < k; ++i) {
                if (!cur) return head;
                cur = cur->next;
            }
            ListNode *new_head = reverse(head, cur);
            head->next = reverseKGroup(cur, k);
            return new_head;
        }
        ListNode* reverse(ListNode* head, ListNode* tail) {
            ListNode *pre = tail;
            while (head != tail) {
                ListNode *t = head->next;
                head->next = pre;
                pre = head;
                head = t;
            }
            return pre;
        }
    };
    
  • 相关阅读:
    Manjaro中添加gitee的公钥部署
    另类的linux系统
    mac的快捷键flykey应用
    tidb总览
    raft算法
    tidb的tidb
    tidb的tikv
    tidb的pd
    切尔诺贝利事故
    血钻
  • 原文地址:https://www.cnblogs.com/wwj99/p/12989774.html
Copyright © 2020-2023  润新知