day1

94 阅读3分钟

两数之和

  • 暴力,两层循环直接比较
  • 双指针,一个指最左一个指最右,用右边的向左迭代,但实际上和暴力没什么差别
  • 建哈希表
// 暴力
vector<int> twoSum(vector<int>& nums, int target) {
        int left=0; 
        int len = nums.size();
        vector<int> res;
        for(; left<len-1; left++){
            for(int right = left+1; right<len; ++right){
                if(nums[left]+nums[right]==target) {
                    res = {left, right};
                    return res;
                }
            }
        }
        return res;
    }
​
// 双指针
vector<int> twoSum(vector<int>& nums, int target) {
        int left=0; 
        int len = nums.size();
        int right = len-1;
        for(; left<len; ++left){
            while(left<right){
                if(nums[left]+nums[right] == target) return {left, right};
                right--;
            }
            right = len-1;
        }
        return {};
    }
​
// 建hash
vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int, int> hash;
        for(int idx=0; idx<nums.size(); ++idx){
            auto tem = hash.find(target-nums[idx]);
            if(tem != hash.end()) return {idx, tem->second};
            hash[nums[idx]] = idx;
        }
        return {};
    }

从提交的结果反馈上, 倒是可以看出双层循环的比较省空间,哈希的则比较省时间

两数相加

这道题一开始想着用一个储存栈记录两个链表的值,弹栈计算出相加的和(sum),再循环sum/10,得到返回链表。写完后发现这样实在是太麻烦,可以用一个数记录每一个位置对应的进位数,直接一次遍历l1、l2,同时记录return就行了

ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        ListNode *head = nullptr, *tail = nullptr;
        int tem = 0;
        while(l1 || l2){
            ListNode* node = nullptr;
            int n1 = l1 ? l1->val : 0;
            int n2 = l2 ? l2->val : 0;
            tem += n1+n2;
            if(tem > 9)  node = new ListNode(tem%10);
            else node = new ListNode(tem);
            tem /= 10;
            if(!head){
                head = node;
                tail = head;
            }else {
                tail->next = node;
                tail = tail->next;
            }
            if(l1) l1 = l1->next;
            if(l2) l2 = l2->next; 
        }
        while(tem){
            tail->next = new ListNode(tem%10);
            tem /= 10;
            tail = tail->next;
        }
        return head;
    }

关于链表节点的访问

首先需要研究节点的定义,一般都是:

/**
 * 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) {}
 * };
 */

要区分的是节点和节点指针,比如ListNode node就是一个节点,要是想通过node访问下一个节点,可以直接node.next,但是对于一个链表,比如ListNode* L,它相当于一个游标,在链表上不停地根据要求变换位置。那么它在新建节点并连接的时候可以写成:

// method 
*L = node;
L = L->next;

要是把node作为一个指针,那么上面两种方法就不需要加上引用or解引用

如果报错是有关内存访问失败的,优先考虑边界条件,然后就是看创建节点的方式是否出错

我一开始用的是创建方式是直接ListNode node; 但是这样其实没有给它分配内存,如果是一个类可以用这种方式创建一个新对象。对于创建结构体的对象,还是用new来吧:比如

ListNode *head = new ListNode(); // 这个小括号中间能不能有值取决于构造函数

无重复字符的最长子串

  • 双指针,一个遍历所有元素,另一个遍历已经走过的元素,比较两者是否出现相同的元素
  • 滑动窗口,可以用一个字符串来记录走过的元素(其实这俩基本都是一个思路)

思路看起来还是挺简单的,但在代码实现的时候有很多细节需要注意(我调了好久):

  1. 不含有重复字符的意思是,“abcb”这样的也不行,每个字符只能出现一次
  2. 对于“dvdf”这样的,在遍历到第二个d时,要从v开始记录
int lengthOfLongestSubstring(std::string s) {
        if(s=="") return 0;
        int idx=0, tag=0, i=0;
        int cnt=0, flag=0;
        while(i<s.size()){
            for(idx=tag; idx<i; ++idx){
                if(s[i]==s[idx]) {
                    tag = ++idx;
                    cnt = i-tag; // 这里cnt直接计算,最好不要试图用类似于cnt++的手段
                    break;
                }
            }
            cnt++;
            i++;
            if(cnt>flag) flag = cnt;
        }
        return flag;
    }

对于滑动窗口,参考中使用的是unordered_set<char>