zoukankan      html  css  js  c++  java
  • Leetcode——链表和数组(3)

    移动零

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

    示例:

    输入: [0,1,0,3,12]
    输出: [1,3,12,0,0]
    

    说明:

    1. 必须在原数组上操作,不能拷贝额外的数组。
    2. 尽量减少操作次数。

    双指针

    用替换法in-place来做,需要用两个指针,一个不停的向后扫,找到非零位置,然后和前面那个指针交换位置即可

    class Solution {
    public:
        void moveZeroes(vector<int>& nums) {
            for (int i = 0, j = 0; i < nums.size(); ++i) {
                if (nums[i]) {
                    swap(nums[i], nums[j++]);
                }
            }
        }
    };
    

    优化

    class Solution {
    public:
        void moveZeroes(vector<int>& nums) {
            int left = 0, right = 0;
            while (right < nums.size()) {
                if (nums[right]) {
                    swap(nums[left++], nums[right]);
                }
                ++right;
            }
        }
    };
    

    移除元素

    给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

    不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组

    元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

    示例 1:

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

    示例 2:

    给定 nums = [0,1,2,2,3,0,4,2], val = 2,
    
    函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。
    
    注意这五个元素可为任意顺序。
    
    你不需要考虑数组中超出新长度后面的元素。
    

    说明:

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

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

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

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

    需要一个变量用来计数,然后遍历原数组,

    如果当前的值和给定值不同,就把当前值覆盖计数变量的位置,并将计数变量加1。

    class Solution {
    public:
        int removeElement(vector<int>& nums, int val) {
            int res = 0;
            for (int i = 0; i < nums.size(); ++i) {
                if (nums[i] != val) nums[res++] = nums[i];
            }
            return res;
        }
    };
    

    移除链表元素

    删除链表中等于给定值 val 的所有节点。

    示例:

    输入: 1->2->6->3->4->5->6, val = 6
    输出: 1->2->3->4->5
    

    遍历

    定义几个辅助指针,然后遍历原链表,

    遇到与给定值相同的元素,将该元素的前后连个节点连接起来,然后删除该元素即可,

    要注意的是还是需要在链表开头加上一个dummy node

    class Solution {
    public:
        ListNode* removeElements(ListNode* head, int val) {
            ListNode *dummy = new ListNode(-1), *pre = dummy;
            dummy->next = head;
            while (pre->next) {
                if (pre->next->val == val) {
                    ListNode *t = pre->next;
                    pre->next = t->next;
                    t->next = NULL;
                    delete t;
                } else {
                    pre = pre->next;
                }
            }
            return dummy->next;
        }
    };
    

    简化

    当判断下一个结点的值跟给定值相同的话,直接跳过下一个结点,

    将next指向下下一个结点,而根本不断开下一个结点的next,更不用删除下一个结点了。

    最后还要验证头结点是否需要删除,要的话直接返回下一个结点

    class Solution {
    public:
        ListNode* removeElements(ListNode* head, int val) {
            if (!head) return NULL;
            ListNode *cur = head;
            while (cur->next) {
                if (cur->next->val == val) cur->next = cur->next->next;
                else cur = cur->next;
            }
            return head->val == val ? head->next : head;
        }
    };
    

    递归

    通过递归调用到链表末尾,然后回来,需要要删的元素,将链表next指针指向下一个元素即可

    class Solution {
    public:
        ListNode* removeElements(ListNode* head, int val) {
            if (!head) return NULL;
            head->next = removeElements(head->next, val);
            return head->val == val ? head->next : head;
        }
    };
    

    删除链表中的节点

    请编写一个函数,使其可以删除某个链表中给定的(非末尾)节点,你将只被给定要求被删除的节点。

    现有一个链表 -- head = [4,5,1,9],它可以表示为:

    img

    示例 1:

    输入: head = [4,5,1,9], node = 5
    输出: [4,1,9]
    解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.
    

    示例 2:

    输入: head = [4,5,1,9], node = 1
    输出: [4,5,9]
    解释: 给定你链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9.
    

    说明:

    • 链表至少包含两个节点。
    • 链表中所有节点的值都是唯一的。
    • 给定的节点为非末尾节点并且一定是链表中的一个有效节点。
    • 不要从你的函数中返回任何结果。

    先把当前节点的值用下一个节点的值覆盖了,然后我们删除下一个节点即可

    C++

    class Solution {
    public:
        void deleteNode(ListNode* node) {
            node->val = node->next->val;
            ListNode *tmp = node->next;
            node->next = tmp->next;
            delete tmp;
        }
    };
    

    Java

    public class Solution {
        public void deleteNode(ListNode node) {
            node.val = node.next.val;
            node.next = node.next.next;
        }
    }
    

    找到所有数组中消失的数字

    给定一个范围在 1 ≤ a[i] ≤ n ( n = 数组大小 ) 的 整型数组,数组中的元素一些出现了两次,另一些只出现一次。

    找到所有在 [1, n] 范围之间没有出现在数组中的数字。

    您能在不使用额外空间且时间复杂度为O(n)的情况下完成这个任务吗? 你可以假定返回的数组不算在额外空间内。

    示例:

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

    方法一

    对于每个数字nums[i]

    如果其对应的nums[nums[i] - 1]是正数,我们就赋值为其相反数,

    如果已经是负数了,就不变了,那么最后我们只要把留下的整数对应的位置加入结果res中即可

    class Solution {
    public:
        vector<int> findDisappearedNumbers(vector<int>& nums) {
            vector<int> res;
            for (int i = 0; i < nums.size(); ++i) {
                int idx = abs(nums[i]) - 1;
                nums[idx] = (nums[idx] > 0) ? -nums[idx] : nums[idx];
            }
            for (int i = 0; i < nums.size(); ++i) {
                if (nums[i] > 0) {
                    res.push_back(i + 1);
                }
            }
            return res;
        }
    };
    

    方法二

    nums[i]置换到其对应的位置nums[nums[i]-1]上去,

    比如对于没有缺失项的正确的顺序应该是[1, 2, 3, 4, 5, 6, 7, 8],而我们现在却是[4,3,2,7,8,2,3,1]

    我们需要把数字移动到正确的位置上去,

    比如第一个4就应该和7先交换个位置,以此类推,最后得到的顺序应该是[1, 2, 3, 4, 3, 2, 7, 8]

    我们最后在对应位置检验,如果nums[i]i+1不等,那么我们将i+1存入结果res中即可

    class Solution {
    public:
        vector<int> findDisappearedNumbers(vector<int>& nums) {
            vector<int> res;
            for (int i = 0; i < nums.size(); ++i) {
                if (nums[i] != nums[nums[i] - 1]) {
                    swap(nums[i], nums[nums[i] - 1]);
                    --i;
                }
            }
            for (int i = 0; i < nums.size(); ++i) {
                if (nums[i] != i + 1) {
                    res.push_back(i + 1);
                }
            }
            return res;
        }
    };
    

    方法三

    nums[nums[i]-1]位置累加数组长度n,注意nums[i]-1有可能越界,所以我们需要对n取余,

    最后要找出缺失的数只需要看nums[i]的值是否小于等于n即可,

    最后遍历完nums[i]数组为[12, 19, 18, 15, 8, 2, 11, 9]

    我们发现有两个数字8和2小于等于n,那么就可以通过i+1来得到正确的结果5和6了

    class Solution {
    public:
        vector<int> findDisappearedNumbers(vector<int>& nums) {
            vector<int> res;
            int n = nums.size();
            for (int i = 0; i < n; ++i) {
                nums[(nums[i] - 1) % n] += n;            
            }
            for (int i = 0; i < n; ++i) {
                if (nums[i] <= n) {
                    res.push_back(i + 1);
                }
            }
            return res;
        }
    };
    

    数组中重复的数据

    给定一个整数数组 a,其中1 ≤ a[i] ≤ nn为数组长度), 其中有些元素出现两次而其他元素出现一次

    找到所有出现两次的元素。

    你可以不用到任何额外空间并在O(n)时间复杂度内解决这个问题吗?

    示例:

    输入:
    [4,3,2,7,8,2,3,1]
    
    输出:
    [2,3]
    

    方法一

    这类问题的核心是就是找nums[i]nums[nums[i] - 1]的关系,

    我们的做法是,对于每个nums[i],我们将其对应的nums[nums[i] - 1]取相反数,

    如果其已经是负数了,说明之前存在过,我们将其加入结果res中即可

    class Solution {
    public:
        vector<int> findDuplicates(vector<int>& nums) {
            vector<int> res;
            for (int i = 0; i < nums.size(); ++i) {
                int idx = abs(nums[i]) - 1;
                if (nums[idx] < 0) res.push_back(idx + 1);
                nums[idx] = -nums[idx];
            }
            return res;
        }
    };
    

    方法二

    nums[i]置换到其对应的位置nums[nums[i]-1]上去,

    比如对于没有重复项的正确的顺序应该是[1, 2, 3, 4, 5, 6, 7, 8]

    而我们现在却是[4, 3, 2, 7, 8, 2, 3, 1],我们需要把数字移动到正确的位置上去,

    比如第一个4就应该和7先交换个位置,以此类推,最后得到的顺序应该是[1, 2, 3, 4, 3, 2, 7, 8]

    我们最后在对应位置检验,如果nums[i]i+1不等,那么我们将nums[i]存入结果res中即可

    class Solution {
    public:
        vector<int> findDuplicates(vector<int>& nums) {
            vector<int> res;
            for (int i = 0; i < nums.size(); ++i) {
                if (nums[i] != nums[nums[i] - 1]) {
                    swap(nums[i], nums[nums[i] - 1]);
                    --i;
                }
            }
            for (int i = 0; i < nums.size(); ++i) {
                if (nums[i] != i + 1) res.push_back(nums[i]);
            }
            return res;
        }
    };
    

    方法三

    nums[nums[i]-1]位置累加数组长度n,注意nums[i]-1有可能越界,所以我们需要对n取余,

    最后要找出现两次的数只需要看nums[i]的值是否大于2n即可,

    最后遍历完nums[i]数组为[12, 19, 18, 15, 8, 2, 11, 9]

    我们发现有两个数字19和18大于2n,那么就可以通过i+1来得到正确的结果2和3了

    class Solution {
    public:
        vector<int> findDuplicates(vector<int>& nums) {
            vector<int> res;
            int n = nums.size();
            for (int i = 0; i < n; ++i) {
                nums[(nums[i] - 1) % n] += n;
            }
            for (int i = 0; i < n; ++i) {
                if (nums[i] > 2 * n) res.push_back(i + 1);
            }
            return res;
        }
    };
    

    寻找重复数

    给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。

    示例 1:

    输入: [1,3,4,2,2]
    输出: 2
    

    示例 2:

    输入: [3,1,3,4,2]
    输出: 3
    

    说明:

    1. 不能更改原数组(假设数组是只读的)。
    2. 只能使用额外的 O(1) 的空间。
    3. 时间复杂度小于 O(n2) 。
    4. 数组中只有一个重复的数字,但它可能不止重复出现一次。

    二分搜索法

    在区间 [1, n] 中搜索,

    首先求出中点 mid,然后遍历整个数组,统计所有小于等于 mid 的数的个数,

    如果个数小于等于 mid,则说明重复值在 [mid+1, n] 之间,

    反之,重复值应在 [1, mid-1] 之间,

    然后依次类推,直到搜索完成,此时的 low 就是我们要求的重复值

    class Solution {
    public:
        int findDuplicate(vector<int>& nums) {
            int left = 1, right = nums.size();
            while (left < right){
                int mid = left + (right - left) / 2, cnt = 0;
                for (int num : nums) {
                    if (num <= mid) ++cnt;
                }
                if (cnt <= mid) left = mid + 1;
                else right = mid;
            }    
            return right;
        }
    };
    

    快慢指针

    由于题目限定了区间 [1,n],所以可以巧妙的利用坐标和数值之间相互转换,

    而由于重复数字的存在,那么一定会形成环,用快慢指针可以找到环并确定环的起始位置,

    class Solution {
    public:
        int findDuplicate(vector<int>& nums) {
            int slow = 0, fast = 0, t = 0;
            while (true) {
                slow = nums[slow];
                fast = nums[nums[fast]];
                if (slow == fast) break;
            }
            while (true) {
                slow = nums[slow];
                t = nums[t];
                if (slow == t) break;
            }
            return slow;
        }
    };
    

    位操作

    遍历每一位,然后对于 32 位中的每一个位 bit,都遍历一遍从0到 n-1,将0到 n-1 中的每一个数都跟 bit 相 ‘与’,若大于0,则计数器 cnt1 自增1。

    同时0到 n-1 也可以当作 nums 数组的下标,从而让 nums 数组中的每个数字也跟 bit 相 ‘与’,

    若大于0,则计数器 cnt2 自增1。

    最后比较若 cnt2 大于 cnt1,则将 bit 加入结果 res 中。

    对于每一位,0到 n-1 中所有数字中该位上的1的个数应该是固定的,

    如果 nums 数组中所有数字中该位上1的个数多了,说明重复数字在该位上一定是1,这样我们把重复数字的所有为1的位都累加起来,就可以还原出了这个重复数字

    class Solution {
    public:
        int findDuplicate(vector<int>& nums) {
            int res = 0, n = nums.size();
            for (int i = 0; i < 32; ++i) {
                int bit = (1 << i), cnt1 = 0, cnt2 = 0;
                for (int k = 0; k < n; ++k) {
                    if ((k & bit) > 0) ++cnt1;
                    if ((nums[k] & bit) > 0) ++cnt2;
                }
                if (cnt2 > cnt1) res += bit;
            }
            return res;
        }
    };
    

    缺失的第一个正数

    给你一个未排序的整数数组,请你找出其中没有出现的最小的正整数。

    示例 1:

    输入: [1,2,0]
    输出: 3
    

    示例 2:

    输入: [3,4,-1,1]
    输出: 2
    

    示例 3:

    输入: [7,8,9,11,12]
    输出: 1
    

    提示:

    你的算法的时间复杂度应为O(n),并且只能使用常数级别的额外空间。

    把1放在数组第一个位置 nums[0],2放在第二个位置 nums[1]

    即需要把 nums[i] 放在 nums[nums[i] - 1]上,

    遍历整个数组,

    如果 nums[i] != i + 1, 而 nums[i] 为整数且不大于n,

    另外 nums[i] 不等于 nums[nums[i] - 1] 的话,将两者位置调换,

    如果不满足上述条件直接跳过,

    最后再遍历一遍数组,如果对应位置上的数不正确则返回正确的数

    class Solution {
    public:
        int firstMissingPositive(vector<int>& nums) {
            int n = nums.size();
            for (int i = 0; i < n; ++i) {
                while (nums[i] > 0 && nums[i] <= n && nums[nums[i] - 1] != nums[i]) {
                    swap(nums[i], nums[nums[i] - 1]);
                }
            }
            for (int i = 0; i < n; ++i) {
                if (nums[i] != i + 1) return i + 1;
            }
            return n + 1;
        }
    };
    

    缺失数字

    给定一个包含 0, 1, 2, ..., nn 个数的序列,找出 0 .. n 中没有出现在序列中的那个数。

    示例 1:

    输入: [3,0,1]
    输出: 2
    

    示例 2:

    输入: [9,6,4,2,3,5,7,0,1]
    输出: 8
    

    说明:
    你的算法应具有线性时间复杂度。你能否仅使用额外常数空间来实现?

    公式

    用等差数列的求和公式求出0到n之间所有的数字之和,

    然后再遍历数组算出给定数字的累积和,

    然后做减法,差值就是丢失的那个数字

    class Solution {
    public:
        int missingNumber(vector<int>& nums) {
            int sum = 0, n = nums.size();
            for (auto &a : nums) {
                sum += a;
            }
            return 0.5 * n * (n + 1) - sum;
        }
    };
    

    位操作

    既然0到n之间少了一个数,我们将这个少了一个数的数组合0到n之间完整的数组异或一下,那么相同的数字都变为0了,剩下的就是少了的那个数字了

    class Solution {
    public:
        int missingNumber(vector<int>& nums) {
            int res = 0;
            for (int i = 0; i < nums.size(); ++i) {
                res ^= (i + 1) ^ nums[i];
            }
            return res;
        }
    };
    
  • 相关阅读:
    接口的显式实现和隐式实现
    MVC
    委托
    测试用例(TestCase)
    The remote server returned an error: NotFound.
    事件
    WCF大数据量传输配置
    多态随笔
    领域模型(domain model)
    IQueryable接口和IEnumberable接口
  • 原文地址:https://www.cnblogs.com/wwj99/p/13030618.html
Copyright © 2011-2022 走看看