不要小看简单题-华为面试题库简单题一题多解整理

写在前面

华为面试题库刷题第二次整理,这次整理的都是简单题的多解法,大部分都是用了巧妙方法使空间复杂度位O(1),简单题也有它的魅力和价值。
个人博客的链接:flamsteed

136. 只出现一次的数字

给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

说明:

你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?

解法:要求时间复杂度O(N),空间复杂度O(1),讲道理,想不出来,只相处一种排序之后遍历一遍得出答案的方法,时间复杂度O(N*log(N)),空间复杂度O(1)。

排序

排序后遍历,没有出现两次就是答案

代码:

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        int res = INT_MAX;
        for(int i=0; i<nums.size();i+=2)
            if(i+1<nums.size() && nums[i]!=nums[i+1]){
                res = nums[i];
                break;
            }
        if(res==INT_MAX) res = nums[nums.size()-1];
        return res;
    }
};

集合

集合可以自动去重,然后求和乘2再减去原来的和就是答案,时间复杂度O(N),空间复杂度O(N)。

代码:

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        set<int> s;
        int sum1(0),sum2(0);
        for(auto x:nums){
            s.insert(x);
            sum1 += x;
        }
        for(auto x:s){
            sum2 += x;
        }
        return (sum2*2-sum1);
    }
};

位运算

又是位运算,这东西是真的骚,反正没做过基本想不出来,不过这次做过了以后就不能忘了。

根据位运算异或的性质,可以得到以下的规律:

  1. a ^ 0 = a;
  2. a ^ a = 0;
  3. a ^ b ^ c = a ^ c ^ b;

所以,根据以上的规律,所有数字异或一边,就会剩下答案。

代码:

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int res = 0;
        for(auto x:nums)
            res ^= x;
        return res;
    }
};

189. 旋转数组

给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数。

示例 1:

输入: [1,2,3,4,5,6,7] 和 k = 3

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

解释:

向右旋转 1 步: [7,1,2,3,4,5,6]

向右旋转 2 步: [6,7,1,2,3,4,5]

向右旋转 3 步: [5,6,7,1,2,3,4]

说明:

尽可能想出更多的解决方案,至少有三种不同的方法可以解决这个问题。

要求使用空间复杂度为O(1)的原地算法。

解法:最朴素的k次移动一位的方法爆了,上了出题者的鬼当,只能O(N)做的话,只能想出来用数组占存一部分,移动好了再填进去。

额外数组存储方法

代码:

class Solution {
public:
    void rotate(vector<int>& nums, int k) {
        if(nums.empty()) return;
        int l = nums.size();
        k = k % l;
        if(k==0) return;
        vector<int> a;
        for(int i=l-k; i<l; i++)
            a.push_back(nums[i]);
        for(int i=l-1; i>=k; i--)
            nums[i] = nums[i-k];
        for(int i=0; i<a.size(); i++)
            nums[i] = a[i];
        return;
    }
};

本地直接交换

题目的正解很巧妙,根据下标i%k的值进行移动,遍历一遍,O(N)解法,而且无额外空间。

代码:

class Solution {
public:
    void rotate(vector<int>& nums, int k) {
        if(nums.empty()) return;
        int l = nums.size();
        k = k % l;
        if(k==0) return;
        
        int count(0);
        int p = 0;
        int current = 0;
        int pre = nums[0];
        while(count<l){
            do{
                int next = (current+k) % l;
                int t = nums[next];
                nums[next] = pre;
                pre = t;
                current = next;
                count++;
            }while(current!=p);
            current = ++p;
            pre = nums[p];
        }
        return;
    }
};

234. 回文链表

请判断一个链表是否为回文链表。

示例 1:

输入: 1->2

输出: false

进阶:

你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?

解法:这题O(N)很简单,但是O(1)空间就有点难了。

存进数组

大部分解法就是存进数组,然后用正常的方法判断回文。

代码:

/**
* Definition for singly-linked list.
* struct ListNode {
*     int val;
*     ListNode *next;
*     ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
    bool isPalindrome(ListNode* head) {
        vector<int> a;
        ListNode* p{head};
        while(p){
            a.push_back(p->val);
            p = p->next;
        }
        bool f = true;
        for(int i=0; i<a.size()/2; i++){
            if(a[i]!=a[a.size()-i-1]){
                f = false;
                break;
            }
        }
        return f;
    }
};

反转后一半

利用快慢指针找到中间点,然后把后面一半的链表反转一下,然后顺序比较。

代码:

/**
* Definition for singly-linked list.
* struct ListNode {
*     int val;
*     ListNode *next;
*     ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
    bool isPalindrome(ListNode* head) {
        if(!head) return true;
        
        ListNode* firstend = secondHalf(head);
        ListNode* Second = reverseList(firstend->next);

        ListNode* p1 = head;
        ListNode* p2 = Second; 

        bool f = true;
        while(f && p2){
            if(p1->val != p2->val) f = false;
            p1 = p1->next;
            p2 = p2->next;
        }
        return f;
    }
    ListNode* reverseList(ListNode* head){//反转
        ListNode* prev = NULL;
        ListNode* curr = head;
        while(curr){
            ListNode* t = curr->next;
            curr->next = prev;
            prev = curr;
            curr = t;
        }
        return prev;
    }
    ListNode* secondHalf(ListNode* head){//寻找中间点
        ListNode* slow{head};
        ListNode* fast{head};
        while(fast->next && fast->next->next){
            slow = slow->next;
            fast = fast->next->next;
        }
        return slow;
    }
};

160. 相交链表

编写一个程序,找到两个单链表相交的起始节点。

输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3

输出:Reference of the node with value = 8

输入解释:相交节点的值为 8 (注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。

注意:

如果两个链表没有交点,返回 null.

在返回结果后,两个链表仍须保持原有的结构。

可假定整个链表结构中没有循环。

程序尽量满足 O(n) 时间复杂度,且仅用 O(1) 内存。

解法:

双向指针

A的指针遍历完A 接着从headB开始遍历

B的指针遍历完B 接着从headA开始遍历

两个指针都最多走m + n + 1步。

当两个指针同时为空时,表示不相交;当两个都非空且相等时,表示相交

时间复杂度O(m + n) 空间复杂度O(1)

代码:

/**
* Definition for singly-linked list.
* struct ListNode {
*     int val;
*     ListNode *next;
*     ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        if(!headA||!headB) return nullptr;
        
        ListNode* curr_a = headA;
        ListNode* curr_b = headB;

        while(curr_a!=curr_b){
            curr_a = (!curr_a) ? headB : curr_a->next;
            curr_b = (!curr_b) ? headA : curr_b->next;
        }
        return curr_a;
    }
};

哈希表法

使用一个hash set 遍历一个链表,set中存放其所有指针, 遍历另一个链表,去set中找相同指针
时间复杂度O(m + n) 空间复杂度O(m) 或 O(n)

代码:

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        unordered_set<ListNode*> s;
        ListNode* curr_a{headA};
        while(curr_a){
            s.insert(curr_a);
            curr_a = curr_a->next;
        }
        ListNode* curr_b{headB};
        while(curr_b){
            if(s.find(curr_b)!=s.end())
                return curr_b;
            curr_b = curr_b->next;
        }
        return nullptr;
    }
};

暴力法有丶傻,不写了。

141. 环形链表

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

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

示例 1:

输入:head = [3,2,0,-4]

输出:true

解释:链表中有一个环,其尾部连接到第二个节点。

解法:

哈希表法

和上一题的哈希表法大同小异,只不过这回是边存边判断。

代码:

/**
* Definition for singly-linked list.
* struct ListNode {
*     int val;
*     ListNode *next;
*     ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
    bool hasCycle(ListNode *head) {
        unordered_set<ListNode*> s;
        ListNode* t = head;
        while(t){
            if(!s.empty() && s.find(t)!=s.end())
                return true;
            s.insert(t);
            t = t->next;
        }
        return false;
    }
};

双指针法

如果存在环路,那么快指针一定会追上慢指针,所以设置两个快慢指针就行了,很巧妙,空间复杂度O(1)。

代码:

class Solution {
public:
    bool hasCycle(ListNode *head) {
        if(!head || !head->next) return false;
        ListNode* slow = head;
        ListNode* fast = head->next;
        while(fast!=slow){
            if(!fast || !fast->next) return false;
            slow = slow->next;
            fast = fast->next->next;
        }
        return true;
    }
};
  • 19
    点赞
  • 86
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

极客不撩妹

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值