日常

专题一:挑战字符串

题目1:无重复字符的最长子串

  • 通过时间:40min
  • 解题思路:滑动窗口,依靠unordered_map实现

题目描述

给定一个字符串,请你找出其中不含有重复字符的最长子串的长度。

示例 1:

输入: "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是`"abc"`,所以其长度为`3`。

示例 2:

输入: "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是`"b"`,所以其长度为`1`。

示例 3:

输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是`"wke"`,所以其长度为`3`。请注意,你的答案必须是`子串`的长度,"pwke" 是一个子序列,不是子串。

算法

  1. brute force:通过两个循环,每次从位置i开始向后遍历,如果出现了一个字符s[j]已经出现过,那么计算j-i的长度是否比已经保存的长度更长,若更长,则替换;并且再次从位置i+1开始向后遍历。
  2. 滑动窗口:位置i遍历过后到j停下,则[i,j)皆是无重复的子串。故再次从i+1遍历时,[i+1,j)这一段无须重复遍历,从j开始即可。
  3. 优化滑动窗口:位置i遍历过后到j停下,说明[i,j)有出现和s[j]一样的字符。找到[i,j)中和s[j]一样字符的位置last_j,将i移到last_j+1,j接着从上次的位置往后一步开始

代码

  1. brute force :略

  2. 滑动窗口: 略

  3. 优化滑动窗口

    class Solution {
    public:
        int lengthOfLongestSubstring(string s) {
            // parameters
            int len = s.length();
            int last_j = 0;
            int longestLen = 0, i = 0, j = 0;
    
            // 滑动窗口
            unordered_map<char, int> myMap;         // key - 字符,value - 在字符串s中的下标
            while (i < len && j < len)
            {
                for (j = last_j; j < len; j++)
                {
                    if (myMap.find(s[j]) == myMap.end())
                        myMap[s[j]] = j;
                    else
                    {
                        // 如果找到的字符是在位置i之前出现过的,只需更新位置,无需理会
                        int index_repeat = myMap[s[j]];
                        if (index_repeat < i)
                        {
                            myMap[s[j]] = j;
                            continue;
                        }
                        
                        // 是否需要更新最长子串的长度
                        int lenOfSub = j - i;
                        longestLen = lenOfSub > longestLen ? lenOfSub : longestLen;
    
                        // 更新i
                        i = index_repeat + 1;
    
                        // 更新map中的映射
                        myMap[s[j]] = j;
                        last_j = j + 1;
                        break;
                    }
                }
                if (j == len)
                {
                    int lenOfSub = j - i;
                    longestLen = lenOfSub > longestLen ? lenOfSub : longestLen;
                }
            }
            return longestLen;
        }
    };
    

题目2:简化路径

  • 通过时间:20min
  • 解题思路:栈

题目描述

以 Unix 风格给出一个文件的绝对路径,你需要简化它。或者换句话说,将其转换为规范路径。

在 Unix 风格的文件系统中,一个点(.)表示当前目录本身;此外,两个点(..)表示将目录切换到上一级(指向父目录);两者都可以是复杂相对路径的组成部分。更多信息请参阅:Linux / Unix中的绝对路径 vs 相对路径

请注意,返回的规范路径必须始终以斜杠 / 开头,并且两个目录名之间必须只有一个斜杠 /。最后一个目录名(如果存在)不能以 / 结尾。此外,规范路径必须是表示绝对路径的最短字符串。

示例 1:

输入:"/home/"
输出:"/home"
解释:注意,最后一个目录名后面没有斜杠。

示例 2:

输入:"/../"
输出:"/"
解释:从根目录向上一级是不可行的,因为根是你可以到达的最高级。

示例 3:

输入:"/home//foo/"
输出:"/home/foo"
解释:在规范路径中,多个连续斜杠需要用一个斜杠替换。

示例 4:

输入:"/a/./b/../../c/"
输出:"/c"

示例 5:

输入:"/a/../../b/../c//.//"
输出:"/c"

示例 6:

输入:"/a//b////c/d//././/.."
输出:"/a/b/c"

算法

粗看题目时并没有什么特别好的想法,看了几个样例后发现了一些端倪。核心思想:将两个斜杠之间出现的‘字符串’压栈但是获取的子字符串不一定是合法的目录名,需要对获得的子字符串判断:

  1. 若字符串为".."进行出栈操作
  2. 若字符串为"."跳过
  3. 若字符串为合法的目录名,则压栈
  4. 当然,过程中多余的/要去掉

代码

  1. canonical path.h

    //
    // Created by shayue on 2019-04-11.
    // 利用栈
    
    #ifndef EXPLORE_BYTEDANCE_CANONICAL_PATH_H
    #define EXPLORE_BYTEDANCE_CANONICAL_PATH_H
    
    #include <string>
    #include <iostream>
    #include <vector>
    using namespace std;
    
    class Solution {
    public:
        string simplifyPath(string path) {
            vector<string> myStack;
            string ret;
    
            // 1. 规范化处理(这步可能有些多余,输入的path应该本身就是合法的,所以不会出现第一个字符不是'/'的情况)
            if (path[0] != '/') 
                path = '/' + path;
    
            // 2. LOOP
            int len = path.size(), i = 0, j;
            while (i < len)
            {
                if (path[i] == '/')
                {
                    i++;
                    continue;
                }
                else
                {
                    string sub;
                    // 获取两个斜杠之间的子字符串
                    for (j = i; j < len && path[j] != '/'; j++)
                        sub += path[j];
                    // 更新i
                    i = j;
    
                    // 判断子字符串的意义
                    if (sub == ".")
                        continue;
                    else if (sub == "..")
                    {
                        if (!myStack.empty())
                            myStack.pop_back();
                    }
                    else
                        myStack.push_back(sub);
                }
            }
            // 3. return
            if (myStack.empty())
                return "/";
            else
            {
                for (i = 0; i < myStack.size(); i++)
                {
                    string adds = "/" + myStack[i];
                    ret += adds;
                }
                return ret;
            }
        }
    };
    
    #endif //EXPLORE_BYTEDANCE_CANONICAL_PATH_H
    
    
  2. main.cpp

    #include <iostream>
    #include "Questions/canonical path.h"
    using namespace std;
    
    int main() {
        Solution s;
        string s1 = "/home/";
        string s2 = "/../";
        string s3 = "/home//foo/";
        string s4 = "/a/./b/../../c/";
        string s5 = "/a/../../b/../c//.//";
        string s6 = "/a//b////c/d//././/..";
    
        cout << s.simplifyPath(s1) << endl;
        cout << s.simplifyPath(s2) << endl;
        cout << s.simplifyPath(s3) << endl;
        cout << s.simplifyPath(s4) << endl;
        cout << s.simplifyPath(s5) << endl;
        cout << s.simplifyPath(s6) << endl;
        
        return 0;
    }
    
  3. 结果
    Snip20190411_11

题目3:复原IP地址

  • 通过时间:25min
  • 解题思路:枚举所有可能

题目描述

给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式。

示例:

输入: "25525511135"
输出: ["255.255.11.135", "255.255.111.35"]

算法

这题一开始我打算用深搜做,但是后来转念一想。取点的位置就那么几个,再怎么复杂都不会超时。下面给出合法IP地址的几个点:

  1. 总的字符串长度len属于[4,12]
  2. 在取3个点将字符串分成4部分,分别判断每部分是不是合法。

那么怎么判断各个部分是不是合法的:

  1. 各个部分的长度小于等于3
  2. 各个部分转成10进制小于等于255
  3. 各个部分如果长度不为1,不能出现0在首位的情况。形如“00”,“010”就是不合法的。而“0”是合法的

代码

  1. Longest Substring Without Repeating Characters.h

    //
    // Created by shayue on 2019-04-11.
    // 给定一串数字,找到所有可能的IP地址。
    // IP地址最多12个数字,分成4部分,且每部分最多为不大于255的数字
    
    #ifndef EXPLORE_BYTEDANCE_RESTORE_IP_ADDRESSES_H
    #define EXPLORE_BYTEDANCE_RESTORE_IP_ADDRESSES_H
    
    #include <string>
    #include <vector>
    using namespace std;
    
    class Solution {
    public:
        vector<string> restoreIpAddresses(string s) {
            vector<string> ret;
            int len = s.length();
    
            // 若传入的数字长度不符合,则不可能形成合法的IP地址
            if (len < 4 || len > 12)
                return ret;
    
            // 随机对这些数字取3个点的位置并判断每部分是否合法,进而选择是否加入返回的序列中
    
            for (int i = 1; i < len && i <= 3; i++)
            {
                // 255.255.11.135
                string part1 = s.substr(0, i);
                if (!isLegal(part1))
                    continue;
                for (int j = 1; i+j < len && j <= 3; j++)
                {
                    string part2 = s.substr(i, j);
                    if (!isLegal(part2))
                        continue;
                    for (int z = 1; i+j+z < len && z <= 3; z++)
                    {
                        string part3 = s.substr(i+j, z);
                        if (!isLegal(part3))
                            continue;
                        string part4 = s.substr(i+j+z);
                        if (!isLegal(part4))
                            continue;
                        else
                        {
                            string IP;
                            IP.append(part1);
                            IP.append(".");
                            IP.append(part2);
                            IP.append(".");
                            IP.append(part3);
                            IP.append(".");
                            IP.append(part4);
                            ret.push_back(IP);
                        }
                    }
                }
            }
    
            return ret;
        }
    
        bool isLegal(string subs)
        {
            int len = subs.size();
            if (len > 3)
                return false;
            if (len == 2 || len == 3)
            {
                if (subs[0] == '0')
                    return false;
            }
            int inter = 0;
            for (int i = 0; i < len; i++)
                inter = inter * 10 + (subs[i] -'0');
    
            return inter <= 255;
        }
    };
    
    #endif //EXPLORE_BYTEDANCE_RESTORE_IP_ADDRESSES_H
    
  2. main.cpp

    #include <iostream>
    #include "Questions/Restore IP Addresses.h"
    using namespace std;
    
    int main() {
        string str = "00000";
        Solution s;
        vector<string> rec = s.restoreIpAddresses(str);
        for (int i = 0; i < rec.size(); i++)
            cout << rec[i] << endl;
        return 0;
    }
    

专题二:数组与排序

题目1:三数之和

  • 通过时间:40min
  • 解题思路:a+b=-c,双指针

题目描述

给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。

注意:答案中不可以包含重复的三元组。

例如, 给定数组 nums = [-1, 0, 1, 2, -1, -4],

满足要求的三元组集合为:
[
  [-1, 0, 1],
  [-1, -1, 2]
]

算法

代码

//
// Created by shayue on 2019-04-11.
//

#ifndef EXPLORE_BYTEDANCE_3SUM_H
#define EXPLORE_BYTEDANCE_3SUM_H

#include <vector>
#include <algorithm>
using namespace std;

#define MIN_INF 0x80000000
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        sort(nums.begin(), nums.end());

        int len = int(nums.size()), last_num_i = MIN_INF;
        int i, j, k;
        vector<vector<int>> ret;

        for (i = 0; i < len - 2; i++)
        {
            if (nums[i] == last_num_i)
                continue;
            else
                last_num_i = nums[i];

            /* 在i之后的位置中寻找nums[j]+nums[k]=-nums[i] */
            k = len - 1;
            j = i + 1;
            while (j < k)
            {
                if (nums[j] + nums[k] == -nums[i])
                {
                    ret.push_back(vector<int>{nums[i], nums[j], nums[k]});
                    int mid = nums[j];
                    last_num_i = nums[k];
                    while (j < k && nums[j] == mid)
                        j++;
                    while (j < k && nums[k] == last_num_i)
                        k--;

                }
                else if (nums[j] + nums[k] < -nums[i])
                    j++;
                else
                    k--;
            }
            while (i+1 < len && nums[i] == nums[i+1])
                i++;
        }
        return ret;
    }
};


#endif //EXPLORE_BYTEDANCE_3SUM_H

/*
 * TEST
 * Solution s;
    vector<int> vec = {-1, 0, 1, 2, -1, -4};
    vector<vector<int> > rec = s.threeSum(vec);
    for (int i = 0; i < rec.size(); i++)
    {
        for (int j = 0; j < rec[i].size(); j++)
            cout << rec[i][j] << ' ';
        cout << endl;
    }
    return 0;
 */

题目2:岛屿的最大面积

  • 通过时间:跪了
  • 解题思路:DFS

题目描述

给定一个包含了一些 0 和 1的非空二维数组 grid , 一个 岛屿 是由四个方向 (水平或垂直) 的 1 (代表土地) 构成的组合。你可以假设二维矩阵的四个边缘都被水包围着。

找到给定的二维数组中最大的岛屿面积。(如果没有岛屿,则返回面积为0。)

示例 1:

[[0,0,1,0,0,0,0,1,0,0,0,0,0],
 [0,0,0,0,0,0,0,1,1,1,0,0,0],
 [0,1,1,0,1,0,0,0,0,0,0,0,0],
 [0,1,0,0,1,1,0,0,1,0,1,0,0],
 [0,1,0,0,1,1,0,0,1,1,1,0,0],
 [0,0,0,0,0,0,0,0,0,0,1,0,0],
 [0,0,0,0,0,0,0,1,1,1,0,0,0],
 [0,0,0,0,0,0,0,1,1,0,0,0,0]]
对于上面这个给定矩阵应返回 6。注意答案不应该是11,因为岛屿只能包含水平或垂直的四个方向的‘1’。

示例 2:

[[0,0,0,0,0,0,0,0]]
对于上面这个给定的矩阵, 返回 0。

注意: 给定的矩阵grid 的长度和宽度都不超过 50。

算法

这题做了我2个小时。一开始傻傻地以为两次dp应该没啥问题,调bug调到后来都怀疑人生了。如果用dp做,应该要4次,从左上到右下,从右下到左上,从右上到左下,从左下到右上。最终某个较大值应该在“岛屿”的“中心”获得。我考虑2两个小时只考虑到前两种,真的是给跪了。考虑到dp还有状态转移方程的判断,这样的话还不如老实用DFS做。事实证明这题用DFS做居然:
屏幕快照 2019-04-12 00.23.54-w521
用DFS做应该要设置一个访问标记数组,因为连在一起的岛屿铁定一次都访问掉了。假设没有进行标记,然后从之前深搜到过位置再进行DFS,肯定走之前相同的路。设置访问标记数组能大大降低时间复杂度。
这题让我反思了很多,做算法题和数学题一样,一定要找准第一步。想到用什么方法,然后去验证可行性,一定能做到事半功倍。千万不能死扣在一种思维上。我的2个小时。。。

代码

//
// Created by shayue on 2019-04-11.
// DFS

#ifndef EXPLORE_BYTEDANCE_MAX_AREA_OF_ISLAND_H
#define EXPLORE_BYTEDANCE_MAX_AREA_OF_ISLAND_H

#include <vector>
#include <algorithm>
using namespace std;

class Solution {
public:
    int visited[60][60];      // 0 - 为访问,1 - 访问
    int maxAreaOfIsland(vector<vector<int>>& grid) {
        int maxArea = 0;
        int row = grid.size();
        int col = grid[0].size();
        for (int i = 0; i < row; i++)
        {
            for (int j = 0; j < col; j++)
            {
                if (grid[i][j] == 1 && visited[i][j] == 0)
                {
                    visited[i][j] = 1;
                    int area = 1;
                    DFS(i, j, row, col, area, grid);
                    if (area > maxArea)
                        maxArea = area;
                }
            }
        }
        return maxArea;
    }

    void DFS(int x, int y, int row, int col, int &area, vector<vector<int>>& grid)
    {
        if (x+1 < row && grid[x+1][y] == 1 && visited[x+1][y] == 0)
        {
            visited[x+1][y] = 1;
            area += 1;
            DFS(x+1, y, row, col, area, grid);
        }

        if (y+1 < col && grid[x][y+1] == 1 && visited[x][y+1] == 0)
        {
            visited[x][y+1] = 1;
            area += 1;
            DFS(x, y+1, row, col, area, grid);
        }

        if (x-1 >= 0 && grid[x-1][y] == 1 && visited[x-1][y] == 0)
        {
            visited[x-1][y] = 1;
            area += 1;
            DFS(x-1, y, row, col, area, grid);
        }

        if (y-1 >= 0 && grid[x][y-1] == 1 && visited[x][y-1] == 0)
        {
            visited[x][y-1] = 1;
            area += 1;
            DFS(x, y-1, row, col, area, grid);
        }
    }
};
#endif //EXPLORE_BYTEDANCE_MAX_AREA_OF_ISLAND_H

题目3:搜索旋转排序数组

  • 通过时间:30min
  • 二分查找

题目描述:

假设按照升序排序的数组在预先未知的某个点上进行了旋转。

( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。

搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。

你可以假设数组中不存在重复的元素。

你的算法时间复杂度必须是 O(log n) 级别。

示例 1:

输入: nums = [4,5,6,7,0,1,2], target = 0
输出: 4

示例 2:

输入: nums = [4,5,6,7,0,1,2], target = 3
输出: -1

算法

虽然数组从某个点被打乱,但是还是局部有序的,这就为使用二分查找提供了前提。还是和一般的二分查找相似,先判断中点和target的关系,然后判断target和两个端点的关系。

代码

//
// Created by shayue on 2019-04-12.
// 二分搜索

#ifndef EXPLORE_BYTEDANCE_SEARCH_IN_ROTATED_SORTED_ARRAY_H
#define EXPLORE_BYTEDANCE_SEARCH_IN_ROTATED_SORTED_ARRAY_H

#include <vector>
#include <iostream>
using  namespace std;

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int len = nums.size();
        int left = 0, right = len - 1;
        while (left <= right)
        {
            int mid = (left + right) / 2;
            if (nums[mid] == target)
                return mid;
            if (nums[left] <= nums[mid] && nums[mid] <= nums[right])
            {
                if (target > nums[mid])
                    left = mid + 1;
                else
                    right = mid - 1;
            }
            else if (nums[left] <= nums[mid] && nums[mid] >= nums[right])
            {
                // 左边是有序的,如target大于nums[mid]只有可能在右边
                if (target > nums[mid])
                    left = mid + 1;
                else
                {
                    if (target >= nums[left])
                        right = mid - 1;
                    else
                        left = mid + 1;
                }
            }
            else if (nums[left] >= nums[mid] && nums[right] >= nums[mid])
            {
                // 右边是有序的,如target小于nums[mid]只有可能在左边
                if (target < nums[mid])
                    right = mid - 1;
                else
                {
                    if (target <= nums[right])
                        left = mid + 1;
                    else
                        right = mid - 1;
                }
            }
        }

        return -1;
    }
};
#endif //EXPLORE_BYTEDANCE_SEARCH_IN_ROTATED_SORTED_ARRAY_H

题目4:朋友圈

  • 通过时间:25min
  • 解题思路:并查集

题目描述

班上有 N 名学生。其中有些人是朋友,有些则不是。他们的友谊具有是传递性。如果已知 A 是 B 的朋友,B 是 C 的朋友,那么我们可以认为 A 也是 C 的朋友。所谓的朋友圈,是指所有朋友的集合。

给定一个 N * N 的矩阵 M,表示班级中学生之间的朋友关系。如果M[i][j] = 1,表示已知第 i 个和 j 个学生互为朋友关系,否则为不知道。你必须输出所有学生中的已知的朋友圈总数。

示例 1:

输入: 
[[1,1,0],
 [1,1,0],
 [0,0,1]]
输出: 2 
说明:已知学生0和学生1互为朋友,他们在一个朋友圈。
第2个学生自己在一个朋友圈。所以返回2。

示例 2:

输入: 
[[1,1,0],
 [1,1,1],
 [0,1,1]]
输出: 1
说明:已知学生0和学生1互为朋友,学生1和学生2互为朋友,所以学生0和学生2也是朋友,所以他们三个在一个朋友圈,返回1。

注意:

N 在[1,200]的范围内。
对于所有学生,有M[i][i] = 1。
如果有M[i][j] = 1,则有M[j][i] = 1。

算法

使用并查集为每位学生找到一个圈子,这个圈子由一个学生编号确定。比如学生1,2,3属于同一个圈子,那么这个圈子有个带头人-编号1,则对编号1,2,3寻找带头人,最终都能指向编号1.这样就形成了一个圈子。

由于输入的矩阵是对称矩阵,遍历的时候只需要遍历对角线上半部分即可。

代码

//
// Created by shayue on 2019-04-12.
//

#ifndef EXPLORE_BYTEDANCE_FRIEND_CIRCLES_H
#define EXPLORE_BYTEDANCE_FRIEND_CIRCLES_H

#include <vector>
#include <iostream>
using namespace std;

class Solution {
public:
    int findCircleNum(vector<vector<int>>& M) {
        // parameters
        int numOfStu = M.size();        // 学生的编号为[0, numOfStu-1]
        int Set[numOfStu];

        // 1. 将并查集初始化,即每个学生的带头人都是其本身
        for (int i = 0; i < numOfStu; i++)
            Set[i] = i;

        // 2. 遍历矩阵M,只需遍历对角线上方即可。当M[i][j]==1时,视情况而定处理编号i和编号j所处的圈子
        for (int i = 0; i < numOfStu; i++)
        {
            for (int j = i+1; j < numOfStu; j++)
            {
                if (M[i][j] == 1)
                {
                    int headi = findHead(i, Set);
                    int headj = findHead(j, Set);
                    if (headi != headj)
                        Set[headj] = headi;         // 当学生i和学生j的圈子未相连时,将他们连在一起
                }
            }
        }
        
        // 确定有几个带头人
        int visited[300] = {0};
        int ret = 0;
        for (int i = 0; i < numOfStu; i++)
        {
            int head = findHead(i, Set);
            if (visited[head] == 0)
            {
                visited[head]++;
                ret++;
            }
        }
        return ret;
    }

    /* 这个函数用来寻找每个学生对应的带头人 */
    int findHead(int pos, const int Set[])
    {
        // 如果这个条件不满足,说明当前编号pos并不是最终的带头人
        while (Set[pos] != pos)
            pos = Set[pos];

        return pos;
    }
};

#endif //EXPLORE_BYTEDANCE_FRIEND_CIRCLES_H

posted @ 2019-04-11 12:05  小王点点  阅读(636)  评论(0编辑  收藏  举报