面试经典 150 题 -- 数组 / 字符串 (总结)

总的链接

面试经典 150 题 - 学习计划 - 力扣(LeetCode)全球极客挚爱的技术成长平台

88.合并两个有效数组

因为有序,直接设置双指针置于两个数组的末尾,从后往前直接模拟就好了,贪心的比较两个指针所指元素,优先挑出大的,放入答案数组中去;

class Solution {
public:
    void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
        int p1 = m - 1, p2 = n - 1;
        int tail = m + n - 1;
        int cur;
        while (p1 >= 0 || p2 >= 0) {
            if (p1 == -1) {
                cur = nums2[p2--];
            } else if (p2 == -1) {
                cur = nums1[p1--];
            } else if (nums1[p1] > nums2[p2]) {
                cur = nums1[p1--];
            } else {
                cur = nums2[p2--];
            }
            nums1[tail--] = cur;
        }
    }
};

27.移除元素

题意 : 在nums中原地移除值=val的数,然后返回新数组的长度;

思路 : 模拟即可,设置双指针,一个j指向新数组的尾下标,一个i指向原数组,一遍遍历,当nums[i]!=val时,将其加入新数组中,否则直接跳过即可;

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int n = nums.size() ;
        int i=0,j=0;
        for(;i<n;i++){
            if(nums[i]!=val) nums[j++] = nums[i];
        }
        return j ;
    }
};

26.删除有序数组中的重复项

思路 : 还是双指针模拟,思路与上题类似; 

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        int j=0;
        int n = nums.size() ;
        
        for(int i=0;i<n;i++){
            int k = i + 1 ;
            while(k<n && nums[k]==nums[i]) k ++;
            nums[j++] = nums[i];
            i = k - 1 ; 
        }
        return j ;
    }
};

80 . 删除有序数组中的重复项 II

思路 : 上一题多了一个保留两个重复元素,那么在枚举的过程中,发现重复的元素数量>=2,那么只将两个加入新数组即可;

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        int j=0;
        int n = nums.size() ;
        for(int i=0;i<n;i++){
            int k = i + 1 ;
            while(k<n && nums[k]==nums[i]) k ++;
            if(k-i>=2){
                nums[j++] = nums[i];
                nums[j++] = nums[i];
            }else{
                nums[j++] = nums[i];
            }
            
            i = k - 1 ; 
        }
        return j ;
    }
};

169.多数元素

法一(众数理论) : 

思路 : 众数理论,由于在题目当中的众数定义为出现次数>=n/2的元素,那么在排好序之后,该元素其中的一个一定会出现在数组最中间 ;

class Solution {
public:
    int majorityElement(vector<int>& nums) {
     sort(nums.begin(),nums.end());
     return nums[nums.size()/2];
    }
};

法二 (哈希表暴力枚举): 

思路 : 利用hash表统计每个元素出现次数,然后枚举哈希表,找到次数最大的元素;这里优化为边遍历边统计;

class Solution {
public:
    int majorityElement(vector<int>& nums) {
        int n = nums.size();
        unordered_map<int,int> counts;
        int cnt = 0;
        int ma;
        for(int num : nums){
            ++counts[num];
            if(counts[num] > cnt){
                ma = num;
                cnt = counts[num];
            }
        }
        return ma;
    }
};

法三(摩尔投票法)

利用摩尔投票法,来解决此题,详情请看代码 : 

class Solution {
    public int majorityElement(int[] nums) {
        int x = 0 , votes = 0 ;
        for(int num : nums){
            if(votes == 0) x = num  ;
            votes += num == x ? 1 : -1 ;
        }
        return x ;
    }
}

189.轮转数组

思路 : 首先先复制nums数组到res中,保留元素组的情况,对于右移k位,那么也就是nums[(i+k)%n] = res[i] ,详细请看代码 : 

class Solution {
public:
    void rotate(vector<int>& nums, int k) {
    int n = nums.size();
    vector<int> res(n);
    for(int i=0;i<n;i++)  res[i] = nums[i];
    for(int i=0;i<n;i++){
        nums[(k+i)%n] = res[i];
    }
    }
};

121 . 买卖股票的最佳时机 

思路 : 一遍遍历,在遍历的过程中,用pre存前面的最小元素,那么对于第i天如果要卖股票的话,那么最大收益就是prices[i]-pre ; 一遍遍历,循环更新答案就好 ;

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n = prices.size() ;
        int mi = prices[0] ;
        int ans = 0 ;
        for(int i=1;i<n;i++){
            ans = max (ans , prices[i] - mi);
            mi = min(mi , prices[i]);
        }
        return ans ;
    }
};

122 . 买卖股票的最佳时机 II

这题就直接遍历就好了,举个例子 :

3 1 5 , 那么ans = 5 - 1

1 2 4 , 那么ans = 4 - 1 = 4-2+2-1

用贪心的思路,相邻两个, 只要p[i]>p[i-1],那么就在前一天买,第i买,就会造成最优的结果 ;

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int ans = 0;
        for(int i=1;i<prices.size();i++){
            int tmp = prices[i] - prices[i-1];
            ans += tmp>0 ? tmp : 0;
        }
        return ans;
    }
};

55.跳跃游戏

一遍遍历 ,记录当前能够达到的最远距离k,如果 i > k 表示到不了,直接返回false即可,在循环的过程中更新k即可;

class Solution {
public:
    bool canJump(vector<int>& nums) {
        int k = 0;
        for(int i=0;i<nums.size();i++){
            if(i>k) return false;
            k = max(k,i+nums[i]);
        }
        return true;
    }
};

45 . 跳跃游戏 II

题目 : 

给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]

每个元素 nums[i] 表示从索引 i 向前跳转的最大长度。换句话说,如果你在 nums[i] 处,你可以跳转到任意 nums[i + j] 处:

  • 0 <= j <= nums[i] 
  • i + j < n

返回到达 nums[n - 1] 的最小跳跃次数。生成的测试用例可以到达 nums[n - 1]

法一 (逆向) : 

反方向查找能够跳到当前位置且距离当前位置最远的点;

class Solution {
    public int jump(int[] nums) {
        // 反方向查找能跳到当前位置且距离当前位置最远的距离
        int p = nums.length - 1 ;
        int ans = 0 ;
        while(p>0){
            for(int i=0;i<p;i++){
                if(i+nums[i]>=p){
                    p = i ;
                    ans ++;
                    break;
                }
            }
        }
        return ans ;
    }
}

法二 : (正向) : 

法一逆向过来,能够优化到O(n)

class Solution {
    public int jump(int[] nums) {
        // 反方向查找能跳到当前位置且距离当前位置最远的距离
        int p = nums.length - 1 ;
        int ans = 0 ;
        int ma = 0 ;
        int r = 0;
        for(int i=0;i<p;i++){
            ma = Math.max(ma , i + nums[i]);
            if(i==r){
                r = ma ;
                ans ++ ;
            }
        } 
        return ans ;
    }
}

274 . H指数 

直接贪心 : 

先排序,从后往前,如果c[i] >= n-i , 那么表示 H = n - i 是可行的,在遍历的过程中更新答案即可 ;

class Solution {
    public int hIndex(int[] c) {
        int ans = 0 ;
        int n = c.length ;
        Arrays.sort(c);
        for(int i=n-1;i>=0;i--){
            if(c[i]>=n-i) {
                ans = Math.max(ans , n-i);
            }
        }
        return ans ;
    }
}

380 . O(1)时间插入,删除和获取随机元素

思路 : 利用边长数组和哈希表实现,对于随机选取,利用rand函数随机生成一个数组下标,满足选取的随机性 ;

class RandomizedSet {
private:
    vector<int> a ;
    unordered_map<int,int> mp ;// key -> 值 , val -> 在a中的下标
public:
    RandomizedSet() {
        srand((unsigned)time(NULL));
    }
    
    bool insert(int val) {
        if(mp.count(val)){
            return false;
        }
        int idx = a.size() ;
        a.push_back(val);
        mp[val] = idx ;
        return true ;
    }
    
    bool remove(int val) {
        if(!mp.count(val)){
            return false;
        }
        int idx = mp[val] ;
        int last = a.back() ;
        a[idx] = last ;
        mp[last] = idx ;
        mp.erase(val);
        a.pop_back() ;
        return true ; 
    }
    
    int getRandom() {
        int randIdx = rand() % a.size() ;
        return a[randIdx];
    }
};

/**
 * Your RandomizedSet object will be instantiated and called as such:
 * RandomizedSet* obj = new RandomizedSet();
 * bool param_1 = obj->insert(val);
 * bool param_2 = obj->remove(val);
 * int param_3 = obj->getRandom();
 */

238 . 除自身以外数组的乘积

由于可能有0的出现,那么就否定了,先求整体的乘积,然后除以自身来得到答案的可能 ;

这里采用前后缀和的思想来解决 ;

class Solution {
public:
    typedef long long LL;
    vector<int> productExceptSelf(vector<int>& nums) {
        int n = nums.size();
        vector<LL> front(n,0);
        vector<LL> back(n,0);
        front[0]=1;
        back[n-1]=1;
        for(int i=1;i<n;i++) front[i]=nums[i-1]*front[i-1];
        for(int j=n-2;j>=0;j--) back[j]=nums[j+1]*back[j+1];
        vector<int> ans(n,0);
        for(int i=0;i<n;i++)   ans[i]=front[i]*back[i];
        return ans;
    }
};

134 . 加油站

本题关键在于 : 

// 假设从x加油站出发经过z加油站最远能到达y加油站,

// 那么从z加油站直接出发,不可能到达y下一个加油站。

// 因为从x出发到z加油站时肯定还有存储的油,或者刚好用完,这都到不了y的下一站,

// 而直接从z出发刚开始是没有存储的油的,所以更不可能到达y的下一站。

// 所以如果 i 最远能够到达 j ,根据上边的结论 i + 1 到 j 之间的节点都不可能绕一圈了。

// 所以直接从 j + 1 开始考虑即可

// 想象成一个圆,所以 i 后边的节点就都不需要考虑了,直接返回 -1 即可。

class Solution {
public:
    int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
        int n = gas.size() ;
        for(int i=0;i<n;i++){
            int j = i ;
            int remain = gas[i] ;
            while(remain-cost[j]>=0){
                remain = remain - cost[j] + gas[(j+1)%n];
                j = (j+1)%n;
                if(j==i){
                    return i ;
                }
            }
            if(j<i){
                return-1 ;
            }
            i = j ;
        }
        return -1 ;
    }
};

135.分发糖果

贪心 , 只用考虑左边和右边相邻且向中心递增且rating小于自己的个数 ;

class Solution {
public:
    int candy(vector<int>& ratings) {
        int n = ratings.size();
        vector<int> left(n,1);
        vector<int> right(n,1);
        int ans = 0;
        for(int i=1;i<n;i++){
            if(ratings[i] > ratings[i-1]){
                left[i] = left[i-1]+1;
            }
        }

        for(int i=n-2;i>=0;i--){
            if(ratings[i]>ratings[i+1]) right[i] = right[i+1]+1;
        }

        for(int i=0;i<n;i++){
            ans += max(left[i],right[i]);
        }

        return ans;
    }
};

42 . 接雨水

对于每个h[i],他上面能够存储的雨水的量只取决于min(L[i] , R[i])-h[i] ;其中L[i]指的是包含i在内及其左边所有柱子最高的高度,R[i]同理,指右边的,那么这里用前后缀数组来解决就行了 ;

class Solution {
public:
    int trap(vector<int>& h) {
        int n = h.size() ;
        vector<int> l(n),r(n);
        l[0] = h[0];
        r[n-1] = h[n-1];
        for(int i=1;i<n;i++) l[i] = max(l[i-1] , h[i]);
        for(int i=n-2;i>=0;i--) r[i] = max(r[i+1] , h[i]);
        int ans = 0 ;
        for(int i=0;i<n;i++){
            ans += min(l[i],r[i])-h[i];
        }
        return ans ;
    }
};

13 . 罗马数字转整数 : 

模拟题,直接模拟就好了!!!

import java.util.*;

class Solution {
    public int romanToInt(String s) {
        int sum = 0;
        int preNum = getValue(s.charAt(0));
        for(int i = 1;i < s.length(); i ++) {
            int num = getValue(s.charAt(i));
            if(preNum < num) {
                sum -= preNum;
            } else {
                sum += preNum;
            }
            preNum = num;
        }
        sum += preNum;
        return sum;
    }
    
    private int getValue(char ch) {
        switch(ch) {
            case 'I': return 1;
            case 'V': return 5;
            case 'X': return 10;
            case 'L': return 50;
            case 'C': return 100;
            case 'D': return 500;
            case 'M': return 1000;
            default: return 0;
        }
    }
}

12 . 整数转罗马数字

上一题的逆运算,也是模拟;

class Solution {
    int[] values = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1};
    String[] symbols = {"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"};

    public String intToRoman(int num) {
        StringBuffer roman = new StringBuffer();
        for (int i = 0; i < values.length; ++i) {
            int value = values[i];
            String symbol = symbols[i];
            while (num >= value) {
                num -= value;
                roman.append(symbol);
            }
            if (num == 0) {
                break;
            }
        }
        return roman.toString();
    }
}

58 . 最后一个单词的长度

先从后向前,找到第一个不是 ' ' 的下标,然后再往前遍历,遇到为' '就停下来,否则继续向前,每次向前,都++ans一次 ;

class Solution {
    public int lengthOfLastWord(String s) {
        int ans = 0;
        int n = s.length()-1;
        while(n>=0 && s.charAt(n)==' ') n--;
        if(n<0) return 0 ;
        while(n>=0 && s.charAt(n)!=' '){
            n--;
            ans ++;
        }
        return ans ;
    }
}

  14 . 最长公共前缀

思路 : 横向扫描,对于本题 : 可以得到以下结论 : 

LCP(S1​…Sn​)=LCP(LCP(LCP(S1​,S2​),S3​),…Sn​)

那么只需遍历一次,每次sj都与之前的LCp(s1,s2,...sj-1)做LCp运算,在遍历的过程中如果发现结果已经是空串,直接返回""即可;

class Solution {
    public String longestCommonPrefix(String[] strs) {
        if(strs.length == 0 || strs == null) return "";

        // 直接横向扫描
        String s = strs[0];
        //iterate each strs[i]
        for(int i = 1; i < strs.length; i++)
        {
           s = findPrefix(s,strs[i]); 
           if(s.length() == 0) break;
        }
        return s;
    }

    public String findPrefix(String s1, String s2)
    {
        int min = Math.min(s1.length(), s2.length());
        int j = 0;
        while(j < min && s1.charAt(j) == s2.charAt(j))
        {
            j ++; 
        }

        return s1.substring(0,j);
    }
}

151 . 反转字符串中的单词

遇到一个反转一个,最后整体反转,就能够得到答案了;

class Solution {
public:
    string reverseWords(string s) {
       int n = s.size();
       int k=0;
       for(int i=0;i<n;i++){
           int j=i;
           while(j<n && s[j]==' ') j++;
           if(j==n) break;
           i=j;
           while(j<n&&s[j]!=' ') j++;
           reverse(s.begin()+i,s.begin()+j);
           if (k) s[k ++ ] = ' ';
           while (i < j) s[k ++ ] = s[i ++ ];
       }
       s.erase(s.begin()+k,s.end());
       reverse(s.begin(),s.end());
       return s;
    }
};

6 . Z字形变换

模拟就好了

class Solution {
public:
    string convert(string s, int numRows) {
        string res;
        if (numRows == 1) return s;
        for (int j = 0; j < numRows; j ++ )
        {
            if (j == 0 || j == numRows - 1)
            {
                for (int i = j; i < s.size(); i += (numRows-1) * 2)
                    res += s[i];
            }
            else
            {
                for (int k = j, i = numRows * 2 - 1 - j - 1;
                        i < s.size() || k < s.size();
                        i += (numRows - 1) * 2, k += (numRows - 1) * 2)
                {
                    if (k < s.size()) res += s[k];
                    if (i < s.size()) res += s[i];
                }
            }
        }
        return res;
    }
};

28 . 找出字符串中第一个匹配项的下标

法一

直接模拟,遍历每一个位置,看是否与匹配项相等 ; 

Java代码 : 

class Solution {
    public int strStr(String h, String n) {
        int k = h.length() , m = n.length() ;
        char[] s = h.toCharArray()  , p = n.toCharArray() ;
        for(int i=0;i<=k-m;i++){
            int a = i , b = 0 ;
            while(b<m && s[a]==p[b]){
                a++;
                b++;
            }
            if(b==m) return i ;
        }
        return -1 ;
    }
}

法二 : 

KMP算法 : 

class Solution {
public:
    int strStr(string s, string p) {
        int n = s.size(), m = p.size();
        if(m == 0) return 0;
        //设置哨兵
        s.insert(s.begin(),' ');
        p.insert(p.begin(),' ');
        vector<int> next(m + 1);
        //预处理next数组
        for(int i = 2, j = 0; i <= m; i++){
            while(j and p[i] != p[j + 1]) j = next[j];
            if(p[i] == p[j + 1]) j++;
            next[i] = j;
        }
        //匹配过程
        for(int i = 1, j = 0; i <= n; i++){
            while(j and s[i] != p[j + 1]) j = next[j];
            if(s[i] == p[j + 1]) j++;
            if(j == m) return i - m;
        }
        return -1;
    }
};

68 . 文本左右对齐

贪心 + 模拟

class Solution {
    // blank 返回长度为 n 的由空格组成的字符串
    string blank(int n) {
        return string(n, ' ');
    }

    // join 返回用 sep 拼接 [left, right) 范围内的 words 组成的字符串
    string join(vector<string> &words, int left, int right, string sep) {
        string s = words[left];
        for (int i = left + 1; i < right; ++i) {
            s += sep + words[i];
        }
        return s;
    }

public:
    vector<string> fullJustify(vector<string> &words, int maxWidth) {
        vector<string> ans;
        int right = 0, n = words.size();
        while (true) {
            int left = right; // 当前行的第一个单词在 words 的位置
            int sumLen = 0; // 统计这一行单词长度之和
            // 循环确定当前行可以放多少单词,注意单词之间应至少有一个空格
            while (right < n && sumLen + words[right].length() + right - left <= maxWidth) {
                sumLen += words[right++].length();
            }

            // 当前行是最后一行:单词左对齐,且单词之间应只有一个空格,在行末填充剩余空格
            if (right == n) {
                string s = join(words, left, n, " ");
                ans.emplace_back(s + blank(maxWidth - s.length()));
                return ans;
            }

            int numWords = right - left;
            int numSpaces = maxWidth - sumLen;

            // 当前行只有一个单词:该单词左对齐,在行末填充剩余空格
            if (numWords == 1) {
                ans.emplace_back(words[left] + blank(numSpaces));
                continue;
            }

            // 当前行不只一个单词
            int avgSpaces = numSpaces / (numWords - 1);
            int extraSpaces = numSpaces % (numWords - 1);
            string s1 = join(words, left, left + extraSpaces + 1, blank(avgSpaces + 1)); // 拼接额外加一个空格的单词
            string s2 = join(words, left + extraSpaces + 1, right, blank(avgSpaces)); // 拼接其余单词
            ans.emplace_back(s1 + blank(avgSpaces) + s2);
        }
    }
};

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java字符串面试通常涉及到String、StringBuilder和StringBuffer类的使用,以下是一些常见的问和答案: 1. String、StringBuilder和StringBuffer有什么区别? - String是不可变的字符序列,每次对String进行修改都会创建一个新的String对象,因此在频繁修改字符串时效率较低。 - StringBuilder和StringBuffer都是可变的字符序列,可以在原有对象上进行修改,因此在频繁修改字符串时效率较高。StringBuilder是非线程安全的,而StringBuffer是线程安全的。 2. String str="i"与 String str=new String("i")一样吗? - 不完全一样。String str="i"会先在常量池中查找是否存在值为"i"的字符串,如果存在则直接返回该字符串的引用;如果不存在则在常量池中创建一个新的字符串"i",并返回该字符串的引用。而String str=new String("i")则会在堆内存中创建一个新的字符串对象,并将该对象的引用赋值给str。 3. 如何将字符串转换为字节数组? - 可以使用getBytes()方法,例如: ```java String str = "hello"; byte[] bytes = str.getBytes(); ``` 4. 如何将字符串转换为字符数组? - 可以使用toCharArray()方法,例如: ```java String str = "hello"; char[] chars = str.toCharArray(); ``` 5. 如何将字符数组转换为字符串? - 可以使用String的valueOf()方法,例如: ```java char[] chars = {'h', 'e', 'l', 'l', 'o'}; String str = String.valueOf(chars); ``` 6. 如何将字符串转换为小写或大写? - 可以使用toLowerCase()和toUpperCase()方法,例如: ```java String str = "Hello"; String lowerStr = str.toLowerCase(); // 输出:hello String upperStr = str.toUpperCase(); // 输出:HELLO ``` 7. 如何将两个字符串拼接起来? - 可以使用concat()方法或"+"运算符,例如: ```java String str1 = "hello"; String str2 = "world"; String str3 = str1.concat(str2); // 输出:helloworld String str4 = str1 + str2; // 输出:helloworld ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值