【leetcode刷题笔记】剑指:哈希表标签

leetcode刷题笔记(剑指:哈希表标签)

文章目录

  • leetcode刷题笔记(剑指:哈希表标签)
    • [面试题 01.01 判定字符是否唯一(easy)](https://leetcode.cn/problems/is-unique-lcci/)
    • [剑指 Offer 03:数组中重复的数字(easy)](https://leetcode.cn/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof/)
    • [剑指 Offer 07:重建二叉树(medium)](https://leetcode.cn/problems/zhong-jian-er-cha-shu-lcof/)
    • [剑指 Offer 39:多数元素(easy)](https://leetcode.cn/problems/majority-element/)
    • [剑指 Offer 35:复杂链表的复制(medium)](https://leetcode.cn/problems/fu-za-lian-biao-de-fu-zhi-lcof/)
    • [剑指 Offer 48:最长不含重复字符的子字符串(medium)](https://leetcode.cn/problems/zui-chang-bu-han-zhong-fu-zi-fu-de-zi-zi-fu-chuan-lcof/) ————————记得看其他解法
    • [剑指 Offer 49:丑数(medium)](https://leetcode.cn/problems/chou-shu-lcof/)
    • [剑指 Offer 50:第一个只出现一次的字符(easy)](https://leetcode.cn/problems/di-yi-ge-zhi-chu-xian-yi-ci-de-zi-fu-lcof/) ————————队列后面记得看
    • [剑指 Offer 52:两个链表的第一个公共节点(easy)](https://leetcode.cn/problems/liang-ge-lian-biao-de-di-yi-ge-gong-gong-jie-dian-lcof/)
    • [剑指 Offer 53-2:0~n-1中缺失的数字(easy)](https://leetcode.cn/problems/que-shi-de-shu-zi-lcof/)

哈希只是种数据结构,算不上是什么思想算法应该,这里只是为了熟悉哈希的用法,要想起这个东西。

面试题 01.01 判定字符是否唯一(easy)

实现一个算法,确定一个字符串 s 的所有字符是否全都不同。

首先很容易想到使用哈希表的方法,此时时间和空间复杂度都为O(n)

class Solution {
public:
    bool isUnique(string s) {
        int n=s.size();
        unordered_map umap;
        for(int i=0;i

采用哈希表是为了记录每个字符出现的次数,但会使用多余的空间。因此可以采用位运算,每一个bit位记录一个字符是否出现,从而不使用额外的数据结构。

class Solution {
public:
    bool isUnique(string s) {
        int n=s.size(),tmp=0,step;
        for(int i=0;i

剑指 Offer 03:数组中重复的数字(easy)

在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。

和上一题基本上一样。首先想到哈希表。但本题不能采用位运算,因为上一题只有小写字母26个,而本题数据范围为2-10000,10000个bit位,会越界?。

class Solution {
public:
    int findRepeatNumber(vector& nums) {
        int n=nums.size();
        unordered_map umap;
        for(int i=0;i

注意题目的条件,他并不是任意的随机一个数组,而是长度为n,且范围在0~n-1。因此可以利用数组的特殊性进行解题优化。

如果没有重复或者缺失,每个元素都应该满足nums[i]=i,每个元素都有它应该在的一个位置。因此利用这个性质,可以依次遍历,把每个元素放在正确的位置上,如果在交换过程中,待交换位置元素已经放置正确,那说明交换位置的nums[i],和待交换位置的nums[nums[i]]相同,因此其为重复元素。

class Solution {
public:
    int findRepeatNumber(vector& nums) {
        int n=nums.size();
        for(int i=0;i

剑指 Offer 07:重建二叉树(medium)

输入某二叉树的前序遍历和中序遍历的结果,请构建该二叉树并返回其根节点。

假设输入的前序遍历和中序遍历的结果中都不含重复的数字。

class Solution {
public:
    unordered_map umap;
    int inx;
    TreeNode* helper(vector& preorder, vector& inorder,int left,int right){
        if(left>right) return nullptr;  

        int root_val=preorder[inx];
        TreeNode* node=new TreeNode(root_val);
        int root_idx_in=umap[root_val];
        
        ++inx;
        node->left=helper(preorder,inorder,left,root_idx_in-1);
        node->right=helper(preorder,inorder,root_idx_in+1,right);

        return node;
    }

    TreeNode* buildTree(vector& preorder, vector& inorder) {
        int n=preorder.size();
        inx=0;
        if(n==0) return nullptr;
        for(int i=0;i

剑指 Offer 39:多数元素(easy)

数组中出现次数超过一半的数字

给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。

你可以假设数组是非空的,并且给定的数组总是存在多数元素。

首先找多数元素,肯定可以想到hash计数

class Solution {
public:
    int majorityElement(vector& nums) {
        unordered_map umap;
        int n=nums.size();
        for(int i=0;in/2) return nums[i];
        }
        return -1;
    }
};

考虑题目的特殊性,多数元素大于元素数量的一半。因此可以先排序,中间元素一定是多数元素。

class Solution {
public:
    int majorityElement(vector& nums) {
        //排序,然后中间的
        sort(nums.begin(),nums.end());
        return nums[nums.size()/2];
    }
};

对于本题,还可以采用投票法。将多数元素看为1,其他为-1,最后相加结果一定大于0。

class Solution {
public:
    int majorityElement(vector& nums) {
        int sum=0,tmp;
        for(int i=0;i

剑指 Offer 35:复杂链表的复制(medium)

请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。

采用哈希表建立各节点的映射关系。

/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* next;
    Node* random;
    
    Node(int _val) {
        val = _val;
        next = NULL;
        random = NULL;
    }
};
*/
class Solution {
public:
    Node* copyRandomList(Node* head) {
        if(head == nullptr) return nullptr;
        Node* node=head;
        unordered_map umap;
        while(node!=nullptr){
            umap[node]=new Node(node->val);
            node=node->next;
        }
        node=head;
        while(node!=nullptr){
            umap[node]->next=umap[node->next];
            umap[node]->random=umap[node->random];
            node=node->next;
        }
        return umap[head];
    }
};

剑指 Offer 48:最长不含重复字符的子字符串(medium) ————————记得看其他解法

请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。

采用unordered_map记录出现的字符,value为字符对应的索引。

采用left记录子字符串的起始索引。当 当前字符s[i]出现过的时候,此时子字符串的长度为i-left,并更新left=umap[s[i]]+1。

此时,需要将left~umap[s[i]]范围内的字符对应value置为0,接着进行下一轮子字符串的遍历查询。

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int n=s.size();
        //哈希表记录已经出现的字符,key:字符 value:索引
        //如果找到了,则将索引idx及之前的 字符的value记为0
        int ans=0;
        int left=0;
        unordered_map umap;
        for(int i=0;i

还有其他解法欸,后面再继续

剑指 Offer 49:丑数(medium)

我们把只包含质因子 2、3 和 5 的数称作丑数(Ugly Number)。求按从小到大的顺序的第 n 个丑数。

标签里加了哈希表,但是感觉用动态规划三指针更好懂。题解里给的哈希表标签是用来建堆时去重的。也去学习下吧!!!不要懒

动态规划做法:下一个丑数是通过前面的丑数*2 *3 *5 然后比较出来的最小的数得到的。每个丑数都需要 *2 *3 *5 ,但是无法保证他的位置。题解里使用了三指针n1,n2,n3分别代表 *2 *3 *5 后的数,而i1,i2,i3是dp数组中的索引,如果乘积后成为了新的丑数或者和新的丑数相等,索引就会+1,由此保证不会有乘积/丑数被遗漏。

class Solution {
public:
    int nthUglyNumber(int n) {
        //首先理解题意,丑数是可以通过只用2 3 5随便怎么乘积可以得到的数
        vectordp(n);
        dp[0]=1;
        int i1=0,i2=0,i3=0;
        for(int i=1;i

最小堆+哈希。关于优先队列,还有堆,其实还不太熟悉。相当于采用堆是把当前丑数都*2 *3 *5 ,然后存入堆中排序,是堆顶的时候取出,有点暴力那味。而动态规划是一个个的按顺序的找。也很明显这个复杂度高很多。

(25条消息) c++优先队列(priority_queue)用法详解_c++优先级队列_吕白_的博客-CSDN博客

class Solution {
public:
    int nthUglyNumber(int n) {
        vector factors = {2, 3, 5};
        unordered_set seen;
        priority_queue, greater> heap; //小顶堆,升序排列的
        seen.insert(1L); //1L就是1,但long类型,不然后面会溢出
        heap.push(1L);
        int ugly = 0;
        for (int i = 0; i < n; i++) {
            long curr = heap.top();
            heap.pop();				//堆顶最小,弹出记录
            ugly = (int)curr;		//第i+1个丑数
            for (int factor : factors) {
                long next = curr * factor;
                //没有出现过,所以插入堆和set中保存,priority_queue会自动排序,本质是个堆实现
                if (!seen.count(next)) {	
                    seen.insert(next);
                    heap.push(next);
                }
            }
        }
        return ugly;
    }
};

//作者:LeetCode-Solution
//链接:https://leetcode.cn/problems/chou-shu-lcof/solution/chou-shu-by-leetcode-solution-0e5i/
//来源:力扣(LeetCode)
//著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

剑指 Offer 50:第一个只出现一次的字符(easy) ————————队列后面记得看

在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。 s 只包含小写字母。

很快就能想到hash表计数啦。时间复杂度是O(n)

class Solution {
public:
    char firstUniqChar(string s) {
        int n=s.size();
        unordered_map umap;
        for(int i=0;i

题解里还提到了队列,平时做题基本上想不到用队列/栈之类的。


剑指 Offer 52:两个链表的第一个公共节点(easy)

输入两个链表,找出它们的第一个公共节点。

最开始能想到的,哈希表先存储一条链的节点,再遍历第二条,如果有一样的就是公共节点。

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        //哈希表先存储一条链的节点,再遍历第二条,如果有一样的就是公共节点
        ListNode *node1=headA,*node2=headB;
        unordered_map umap;
        while(node1!=nullptr){
            umap[node1]++;
            node1=node1->next;
        }
        while(node2!=nullptr){
            if(umap[node2]) return node2;
            node2=node2->next;
        }
        return nullptr;
    }
};

采用哈希会用到额外的空间,可以考虑双指针的方法。这个也好理解,反向来推的话,比如两个链表长度分别为len1,len2,两个指针node1,node2分别从两条链表起始开始,走到头后又从另一条链表开始,那么他们都走过的长度是len1+len2,相交节点到末尾距离令为tmp,是他们最后都为经历的,因此他们都会在len1+len2-tmp步长的时候相遇。再正向去想······感觉有点没解释清楚。

评论区真的——浪漫的嘞。

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode *node1=headA,*node2=headB;
        while(node1!=node2){
            if(node1!=nullptr) node1=node1->next;
            else node1=headB;
            if(node2!=nullptr) node2=node2->next;
            else node2=headA;
        }
        return node2;  
    }
};

剑指 Offer 53-2:0~n-1中缺失的数字(easy)

一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内。在范围0~n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。

给了用哈希表的标签,官方题解就是把nums中元素放到set里面,然后遍历0~n-1看缺哪个。。只能说很抽象没必要吧。还不如直接遍历。

第一反应就是遍历数组,因为已经有序,如果不缺的话nums[i]=i,如果不满足那么缺的就是i呀。然后考虑边界情况。这个应该算暴力法吧。

class Solution {
public:
    int missingNumber(vector& nums) {
        //已经有序
        int n=nums.size();
        for(int i=0;i

上面这个方法时间复杂度O(n)。因为已经有序,还可以考虑二分,降低复杂度。二分一个很重要的要判断边界问题,什么时候去等什么时候不取等!!!

class Solution {
public:
    int missingNumber(vector& nums) {
        //采用二分实际上还是对nums[i]==i进行判断,只是不是全部遍历罢了
        int left=0,right=nums.size()-1;
        while(left

数学方法,就是0~n-1的和可求,原数组和也可求,两个相减就是缺的数,时间复杂度也为O(n)。没必要写。

位运算,两个一样的数异或为0,异或具有交换组合性质。因此可以将数组的数相异或,再和0~n-1异或。最后剩的就是差的那个。

class Solution {
public:
    int missingNumber(vector& nums) {
        int n=nums.size();
        int ans=0;
        for(int i=0;i

你可能感兴趣的:(leetcode,leetcode,笔记,哈希表,c++)