LeetCode热题100道

文章目录

  • 1、两数之和
  • 2、两数相加
  • 3、无重复字符的最长子串
  • 4、最长回文子串
  • 5、盛最多水的容器
  • 6、删除链表的倒数第 N 个结点
  • 7、Day 2 【未完待续】

1、两数之和

通往【LeetCode - 两数之和】的任意门

解法一:暴力解
采用两层for循环,第一次层循环选取第一个数,第二次循环选取第二个数,第二层循环找到符合的数则直接返回结果。
时间复杂度:O(n^2)
空间复杂度:O(1)

vector<int> twoSum(vector<int>& nums, int target) {
        vector<int> res;
        for (int i = 0; i < nums.size(); i ++) {
            for (int j = i + 1; j < nums.size(); j ++) {
                if (nums[i] + nums[j] == target) {
                    res.push_back(i);
                    res.push_back(j);
                    return res;
                }
            }
        }
        return res;
    }

解法二:使用map
第一次遍历数组,初始化map,判断map是否存在key为nums[i]的键值对,不存在则将(nums[i], i)插入map中,存在则继续下一轮循环。
第二次遍历数组,若能够在map中找到key为target - nums[i],且value != i的键值对,则返回结果。
时间复杂度:O(n)
空间复杂度:O(n)

 vector<int> twoSum(vector<int>& nums, int target) {
        vector<int> res;
        // 定义map并初始化
        map<int, int> numMap; 
        for (int i = 0; i < nums.size(); i ++) {
            if (numMap.find(nums[i]) == numMap.end()) {
                numMap.insert(pair<int, int>(nums[i], i));
            }
        }

        for (int i = 0; i < nums.size(); i ++) {
            map<int, int>::iterator it = numMap.find(target - nums[i]);
            // 注意第二个条件,保证两个下标不同
            if (it != numMap.end() && (*it).second != i) {
                res.push_back(i);
                res.push_back((*it).second);
                return res;
            }
        }
        return res;
    }

2、两数相加

通往【LeetCode - 两数相加】的任意门

两个链表相加,主要要解决两个链表长度不一致以及进位的问题。
长度不一致问题:可分为三种情况,第一,两个链表都还有未处理的节点;第二,只有链表一还有未处理的节点;第三,只有链表二还有未处理的节点。
进位问题:设置一个变量标记低一位的运算是否有进位。

ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
    // 判断边界条件
    if (l1 == NULL || l2 == NULL) {
        return l1 == NULL ? l2 : l1;
    }
    // 创建存储结果链表的虚拟头结点
    ListNode *head = new ListNode(-1, NULL);
    ListNode *cur = head;
    // 前一位的进位
    int addBit = 0;
    
    // l1与l2都还有未计算的结点
    while (l1 != NULL && l2 != NULL) {
        int res = l1->val + l2->val + addBit;
        if (res >= 10) {
            addBit = 1;
            cur->next = new ListNode(res - 10, NULL);
        } else {
            addBit = 0;
            cur->next = new ListNode(res, NULL);
        }
        cur = cur->next;
        l1 = l1->next;
        l2 = l2->next;
    }

    // 仅l1还有未计算的结点
    while (l1 != NULL) {
        int res = l1->val + addBit;
        if (res >= 10) {
            addBit = 1;
            cur->next = new ListNode(res - 10, NULL);
        } else {
            addBit = 0;
            cur->next = new ListNode(res, NULL);
        }
        cur = cur->next;
        l1 = l1->next;
    }

    // 仅l2还有未计算的结点
    while (l2 != NULL) {
        int res = l2->val + addBit;
        if (res >= 10) {
            addBit = 1;
            cur->next = new ListNode(res - 10, NULL);
        } else {
            addBit = 0;
            cur->next = new ListNode(res, NULL);
        }
        cur = cur->next;
        l2 = l2->next;
    }

    // 判断最后是否有进位
    if (addBit == 1) {
        cur->next = new ListNode(1, NULL);
    }

    ListNode* temp = head->next;
    
    // 释放虚拟头结点
    head->next = NULL;
    delete head;
    head = NULL;
    
    return temp;
}

3、无重复字符的最长子串

通往【LeetCode - 无重复字符的最长子串】的任意门

利用map缓存当前无重复字符的字符及其对应位置,采用滑动窗口的思想求出无重复字符子串的最大长度

int lengthOfLongestSubstring(string s) {
    if (s == "") {
        return 0;
    }
    
    // 将字符串转化为char数组
    char buf[s.size()];
    s.copy(buf, s.size());
    
    // 用于记录当前子串的字符及对应下标
    map<char, int> charMap;

    // 用于记录无重复字符的子串的最大值
    int max = 0;
    // 当前子串的开始与结束位置,[left, right)
    int left = 0, right = 0;

    // 利用滑动窗口的思想求无重复字符的最长子串
    while (right < s.size()) {
        // 判断是否出现重复字符
        map<char, int>::iterator it = charMap.find(buf[right]);
        if (it != charMap.end()) {
            // 存在则将left 到 前一次出现该字符的位置 的字符从map中删除
            int val = (*it).second;
            for (int i = left; i <= val; i++) {
                charMap.erase(buf[i]);
            }
            // 更新max与left
            max = max > (right - left) ? max : (right - left);
            left = val + 1;
            charMap.insert(pair<char,int>(buf[right], right));
        } else {
            charMap.insert(pair<char,int>(buf[right], right));
        }
         right ++;
    }
    // 最后判断是否需要更新max
    max = max > (right - left) ? max : (right - left);        
    return max;
}

4、最长回文子串

通往【LeetCode - 最长回文子串】的任意门

解法一:暴力枚举

string longestPalindrome(string s) {
    // 处理边界条件
    if (s.size() < 2) {
        return s;
    }

    // 将字符串转化为char数组
    char buf[s.size()];
    s.copy(buf, s.size());
    
    // 最长回文子串的长度,起始位置,结束位置
    int maxLen = 0;
    // 最长回文子串buf[start, end]
    int start = 0, end = -1;
    // 暴力枚举,枚举每个位置的字符都作为起始位置和结束位置
    for(int i = 0; i < s.size(); i ++) {
        for(int j = i; j < s.size(); j ++) {
            // 先判断当前子串是否大于最大长度
            // 若当前子串长度小于最大长度,则不需要进行是否为回文串的判断
            if ((j - i + 1) > maxLen && palindrome(buf, i, j)) {
                maxLen = j - i + 1;
                start = i;
                end = j;
            }
        }
    }
    return s.substr(start, maxLen);
}


// 判断buf[low, high]是否为回文子串
bool palindrome(char *buf, int low, int high) {
     while (low < high) {
         if (buf[low] != buf[high]) {
             return false;
         }
         low ++;
         high --;
     } 
     return true;
 }

解法二:中心扩散法

string longestPalindrome(string s) {  
    // 处理边界条件 
    if (s.size() < 2) {
        return s;
    }

    // 将字符串转化为char数组
    char buf[s.size()];
    s.copy(buf, s.size());

    // 最大回文子串的长度,起始位置,结束位置
    int maxLen = 1;
    int start = 0, end = 0;

    // 遍历字符,以每个字符作为回文串的中心进行扩展
    int low = 0, high = 0;
    for(int i = 0; i < s.size(); i ++) {
        low = i;
        high = i;
        // 若中心字符左右相邻两边存在与其相同的字符,则将这些相同的字符一起作为扩散的中心
        while (low - 1 >= 0 && buf[i] == buf[low - 1]) low --;
        while (high + 1 < s.size() && buf[i] == buf[high + 1]) high ++;
        
        // 两边扩散
        while (low > 0 && high < s.size() - 1 && buf[low - 1] == buf[high + 1]) {
            low --;
            high ++;
        }

        // 判断是否更新最大回文串信息
        if (high - low + 1 > maxLen) {
            maxLen = high - low + 1;
            start = low;
            end = high;
        }
    }

    return s.substr(start, maxLen);
}

5、盛最多水的容器

通往【LeetCode - 盛最多水的容器】的任意门

解法:贪心算法 - 双指针
可以将容积抽象为面积大小,因此决定容积大小的因素有两个:第一,两根柱子之间的距离;第二,构成容器的两根柱子的高度的较小值。
因为容积大小由两个因素共同决定,我们无法直接使用贪心算法。如果要使用贪心算法,那么我们必须让遍历过程中有其中一个因素是从最优逐渐衰减的。首先想到的是排序,但由于排序会打乱柱子之间的顺序,从而影响到因素一。那么,我们应该让遍历过程中柱子之间的距离是从大到小的,因此我们初始化两个指针,分别指向数组头部和尾部。
最后需要解决的是,在遍历过程中如何贪心柱子高度才能使结果最优。由于柱子之间的距离是逐渐减小的,头尾指针中的较小者不可能再次成为答案,所以我们可以让高度较小的指针移动,直至头尾指针相遇,则得出最优解。
时间复杂度:O(n)
空间复杂度:O(1)

int maxArea(vector<int>& height) {
    int max = 0, l = 0, r = height.size() - 1;
    int maxHeight = 0;
    
    while (l < r) {
    	// 计算当前的容积,并判断是否需要更新容积最大值
        int min = height[l] > height[r] ? height[r] : height[l];
        int area = min * (r - l);
        if (area > max) {
            max = area;
            maxHeight = min;
        }
        
        // 高度较小者移动
        if (height[l] < height[r]) {
           l ++;
        } else {
           r --;
        }
    }

    return max;
}

6、删除链表的倒数第 N 个结点

通往【LeetCode - 删除链表的倒数第 N 个结点】的任意门

解法:双指针
在单链表中,假定要删除倒数第2个结点,我们需要其前驱结点,即倒数第3个结点。
由于删除的结点有可能是头结点,为方便统一处理,我们设置虚拟头结点(-1)
假定链表为[1, 2, 3, 4, 5],需要删除倒数第2个结点

  1. 第一步:最初p,q指针都指向虚拟头结点
    p↓ ↓q
    (-1) -> 1 -> 2 -> 3 -> 4 -> NULL
  2. 第二步:先让指针q向后移动 2 + 1 次
    p↓ ↓q
    (-1) -> 1 -> 2 -> 3 -> 4 -> NULL
  3. 第三步:指针p,q同步向后移动,当q = NULL时则结束循环
    p↓ ↓q
    (-1) -> 1 -> 2 -> 3 -> 4 -> NULL
  4. 第四步:删除倒数第2个结点,返回 虚拟头结点->next
    p->next = p->next->next;
    return 虚拟头结点->next;

时间复杂度:O(n)
空间复杂度:O(1)

ListNode* removeNthFromEnd(ListNode* head, int n) {
    // 创建虚拟头结点,并将p,q指针都指向虚拟头结点
    ListNode* p = new ListNode(-1, head);
    ListNode* h = p;
    ListNode* q = p;

    while (n > -1) {
        q = q->next;
        n --;
    }

    while (q != NULL) {
        q = q->next;
        p = p->next;
    }

    // 删除并释放倒数第n个结点
    ListNode* temp = p->next;
    p->next = p->next->next;
    delete temp;
    temp = NULL;
    
    // 删除并释放虚拟头结点
    temp = h->next;
    delete h;
    h = NULL;

    return temp;
}

7、Day 2 【未完待续】

你可能感兴趣的:(算法,leetcode,链表,算法)