算法总结(链表、哈希)

引言

19. 删除链表的倒数第 N 个结点 - 力扣(LeetCode)

142. 环形链表 II - 力扣(LeetCode)

242. 有效的字母异位词 - 力扣(LeetCode)

349. 两个数组的交集 - 力扣(LeetCode)

202. 快乐数 - 力扣(LeetCode)

1. 两数之和 - 力扣(LeetCode)

第一题

这一题的本质是双指针,而之所以要用双指针就是因为删除一个结点的特点是需要知道删除结点的前面一个结点。这一题的一个特点就是给出的结点是倒数的结点,所以我们双指针中的慢指针指向的是要删除的结点,而快指针利用倒数这个特点,当快指针到达nullptr的时候,慢指针正好到达我们要删除的目的结点的前面一个节点。对于这个情况,我们可以模拟一下,倒数的结点是n,那么快指针要比慢指针多走n+1个结点,所以先让快指针先走,然后两个指针同步。

cpp 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* dummyHead = new ListNode();
        dummyHead->next = head;
        ListNode* slow = dummyHead;
        ListNode* fast = dummyHead;
        n++;
        while(n--) {
            fast = fast->next;
        }
        while(fast != nullptr) {
            fast = fast->next;
            slow = slow->next;
        }
        ListNode* temp = slow->next;
        slow->next = temp->next;
        delete temp;
        return dummyHead->next;
    }
};

第二题

这是一道很经典的找环的入口的题目,首先环的特点就是没有出口,所以我们采用快慢指针的方法解决这个问题。原理就是当快指针先环里面转圈,只要慢指针进入了这个环,那么总有一天这两个指针会相遇。而我们快指针的速度是一次两格,慢指针是一次一格,所以速度正好是两倍的关系,所以在相同的时间内,路程也是两倍的关系。正因为是这个样子,快指针一定是在慢指针进入环内第一圈的时候就追上了慢指针。一个比较直观的理解:快指针比慢指针多走一格,他们之间的距离不可能比圈的长度要大。

但是相遇之后这并不是我们想要的环的入口,假设起点到环的距离是x,满指针在环走了y,还剩z,那么对于慢指针,走了x+y,快指针走了,x + n(y+z)+ z,而由于距离是2倍的关系,所以有

(x + y) * 2 = x + y + n (y + z),化简之后是x = (n - 1) (y + z) + z

这个东西可能看起来比较丑,但是仔细一看,y+z就是环的大小,z就是快指针再跑z的路程就回到了环的入口,而x不就是从起点到入口的距离,也就是说最后如果一个指针从头开始跑,一个指针从相遇的位置开始跑,速度是1,那么相遇的位置一定是入口处,只不过是多跑了n-1圈的事情.

最后要注意的是while()的条件,因为可能没有环,而fast的速度是2,所以判断条件要写两个

cpp 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode* slow = head;
        ListNode* fast = head;
        while(fast != nullptr && fast->next != nullptr) {
            fast = fast->next->next;
            slow = slow->next;
            if (fast == slow) {
                ListNode* index1 = fast;
                ListNode* index2 = head;
                while(index1 != index2) {
                    index1 = index1->next;
                    index2 = index2->next;
                }
                return index1;
            }
        }
        return nullptr;
    }
};

第三题

这一题考察的是哈希表,记录每一个字母出现的频率,然后把这两个数组的数据进行比较。我们对于哈希问题的处理一般是set和map来解决,但是这里我们选择是数组来解决,因为我们的总范围是确定的,所以数组完全可以解决。

cpp 复制代码
class Solution {
public:
    bool isAnagram(string s, string t) {
        int record[26] = {0};
        if (s.size() != t.size()) {
            return false;
        }
        for(int i = 0; i  < s.size(); i++) {
            record[s[i] - 'a']++;
        }
        for(int i = 0; i < t.size(); i++) {
            record[t[i] - 'a']--;
        }
        for(int i = 0; i < 26; i++) {
            if(record[i] != 0) {
                return false;
            }
        }
        return true;
    }
};

第四题

这一题解决哈希问题我们选择的是set集合,一个set是存结果,一个set是存其中一个数组,用来去重,我们一定要注意的是结果这个数组也是set,因为如果是一般的数组,那么这个数组里面就会有重复的数据。不过因为返回的是数组,所以我们最后要记得强转一下即可

cpp 复制代码
class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        unordered_set<int> result_set;
        unordered_set<int> nums_set(nums1.begin(), nums1.end());
        for (int num : nums2) {
            if (nums_set.find(num) != nums_set.end()) {
                result_set.insert(num);
            }
        }
        return vector<int>(result_set.begin(), result_set.end());
    }
};

第五题

这一题的破题关键就是判断我们求出的和是不是之前出现过的,如果之前出现过这个和,那么说明陷入了循环,永远不会为1。那么我们要快速找出一个数是否之前出现过,用哈希最合适

cpp 复制代码
class Solution {
public:
    int getSum(int n) {
        int sum = 0;
        while(n) {
            sum += (n % 10) * (n % 10);
            n /= 10;
        }
        return sum;
    }

    bool isHappy(int n) {
        unordered_set<int> set;
        while(1) {
            int sum = getSum(n);
            if (sum == 1) {
                return true;
            }
            if (set.find(sum) == set.end()) {
                set.insert(sum);
            } else {
                return false;
            }
            n = sum;
        }
    }
};

第六题

首先,我们这一题的目的就是知道一个值,然后判断另一个值是否在数组里面存在,这个判断数据是否存在的问题,我们一般选择哈希来解决。然后就是存储数据的数据结构,我们选择map,但是还有一个要注意的是,我们查找find()函数都是key值,所以我们需要把数据放在key的位置,下标放在value的位置,不一定说key每次都要从1,2,3。。。开始

cpp 复制代码
class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int, int> map;
        for(int i = 0; i < nums.size(); i++) {
            auto it = map.find(target - nums[i]);
            if (it != map.end()) {
                return {it->second, i};
            } else {
                map[nums[i]] = i;
            }
        }
        return {};
    }
};

总结

在文章的最后,还有一个细节需要被提及,就是虚拟头节点。无论是我们构建链表,还是做链表的算法题也好,虚拟头节点一直是我们无法回避的一个点,不是说没有虚拟头节点我们做不了,而是可以更加的方便,可以把所有的操作全部统一而且不需要考虑空链表报错的情况

本篇文章就到这里结束了,希望可以对大家有所帮助~~~