玩转算法面试:(三)LeetCode数组类问题

数组中的问题其实最常见。

排序:选择排序;插入排序;归并排序;快速排序
查找:二分查找法
数据结构:栈;队列;堆

1、二分查找法

template<typename T>
int binarySearch( T arr[], int n, T target )
{    
    int l = 0, r = n-1; // 在[l...r]的范围里寻找target:前闭后闭
    while( l <= r )
    {    
        // 只要还有可以查找的内容。当 l == r时,区间[l...r]依然是有效的
        int mid = l + (r-l)/2;        
        if( arr[mid] == target ) 
            return mid;        //mid已经判断过了
        if( target > arr[mid] )
            l = mid + 1;  // target在[mid+1...r]中; [l...mid]一定没有target
        else    // target < arr[mid]
            r = mid - 1;  // target在[l...mid-1]中; [mid...r]一定没有target
    }    
    return -1;
}
改变变量的定义,依然可以写出正确的算法
template<typename T>
int binarySearch( T arr[], int n, T target )
{    
    int l = 0, r = n; // target在[l...r)的范围里,这样设置才能保证长度为n
    while( l < r )
    {    
        // 当 l == r时,区间[l...r)是一个无效区间 [42,43)
        int mid = l + (r-l)/2;        
        if( arr[mid] == target ) 
            return mid;        
        if( target > arr[mid] )
            l = mid + 1;    // target在[mid+1...r)中; [l...mid]一定没有target
        else// target < arr[mid]
            r = mid;        // target在[l...mid)中; [mid...r)一定没有target
    }    
    return -1;
}

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

给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素最多出现两次,返回移除后数组的新长度。

不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。

示例 1:

给定 nums = [1,1,1,2,2,3],

函数应返回新长度 length = 5, 并且原数组的前五个元素被修改为 1, 1, 2, 2, 3 。

你不需要考虑数组中超出新长度后面的元素。

示例 2:

给定 nums = [0,0,1,1,1,1,2,3,3],

函数应返回新长度 length = 7, 并且原数组的前五个元素被修改为 0, 0, 1, 1, 2, 3, 3 。

你不需要考虑数组中超出新长度后面的元素。

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

75. 颜色分类

给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。

此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。

注意:
不能使用代码库中的排序函数来解决这道题。

示例:

输入: [2,0,2,1,1,0]
输出: [0,0,1,1,2,2]

进阶:

    一个直观的解决方案是使用计数排序的两趟扫描算法。
    首先,迭代计算出0、1 和 2 元素的个数,然后按照0、1、2的排序,重写当前数组。
    你能想出一个仅使用常数空间的一趟扫描算法吗?

(1)计数排序,时间复杂度O(n)

class Solution {
public:
    void sortColors(vector<int>& nums) {
        int n = nums.size();
        if(n <= 1)
            return;
        vector<int> count(3, 0);
        for(int i = 0; i < n; i++)
            count[nums[i]]++;
        
        int k = 0;
        for(int i = 0; i < 3; i++)
        {
            while(count[i] > 0)
            {
                nums[k++] = i;
                count[i]--;
            }
        }
    }
};

(3)三路快排,空间复杂度O(1)

设置三个索引

class Solution {
public:
    void sortColors(vector<int>& nums) {
        int n = nums.size();
        if(n <= 1)
            return;
        int start = -1, end = n;
        
        int i = 0;
        while(i < end)
        {
            if(nums[i] == 1)
                i++;
            else if(nums[i] == 0)
            {
                swap(nums[++start], nums[i++]);
            }
            else
                swap(nums[--end], nums[i]);
        }
    }
};

215. 数组中的第K个最大元素

在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

示例 1:

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

示例 2:

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

说明:

你可以假设 k 总是有效的,且 1 ≤ k ≤ 数组的长度。

(1)第一种方法,使用堆排序方法

建立一个大小为k的最小堆,时间复杂度klogk。然后将剩余的n-k个数和堆顶比较,如果大于堆顶则放入堆中,并调整最小堆。时间复杂度O((n-k)logk),总的时间复杂度O(nlogk)。

class Solution {
public:
    void adjust(vector<int> &MinHeap, int p, int N)
    {
        int parent, child;
        int temp = MinHeap[p];
        for(parent = p; (2*parent+1) < N; parent = child)
        {
            child = 2*parent+1;
            if(child+1 < N && MinHeap[child] > MinHeap[child+1])
                child++;
            if(temp < MinHeap[child])
                break;
            else
                MinHeap[parent] = MinHeap[child];
        }
        MinHeap[parent] = temp;
    }
    int findKthLargest(vector<int>& nums, int k) {
        int n = nums.size();
        if(n == 0 && n < k)
            return -1;
        vector<int> MinHeap(k);
        for(int i = 0; i < k; i++)
            MinHeap[i] = nums[i];
        for(int i = k/2-1; i >=0 ;i--)
            adjust(MinHeap, i, k);
        for(int i = k;i < n; i++)
        {
            if(nums[i] > MinHeap[0])
            {
                MinHeap[0] = nums[i];
                adjust(MinHeap, 0, k);
            }
        }
        return MinHeap[0];
    }
};

(2)快排,但不需要全部排序,只要partition就可以了

class Solution {
public:
    int partition(vector<int> &nums, int start, int end, int k)
    {
        int pivot = nums[start];
        int i = start, j =end;
        while(i < j)
        {
            while(i < j && nums[j] >= pivot)
                j--;
            nums[i] = nums[j];
            while(i < j && nums[i] < pivot)
                i++;
            nums[j] = nums[i];
        }
        nums[i] = pivot;
        if(i == k)
            return nums[i];
        else if(i < k)
            return partition(nums, i+1, end, k);
        else
            return partition(nums, start, i-1, k);
    }
    int findKthLargest(vector<int>& nums, int k) {
        int n = nums.size();
        if(n == 0 && n < k)
            return -1;
        return partition(nums, 0, n-1, n-k);
    }
};

11. 盛最多水的容器

给定 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

说明:你不能倾斜容器,且 n 的值至少为 2。

图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。

示例:

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

使用双指针

class Solution {
public:
    int maxArea(vector<int>& height) {
        int n = height.size();
        if(n < 2)
            return 0;
        int left = 0, right = n-1;
        int res = 0;
        while(left < right)
        {
            int temp =(right - left)*(height[left] < height[right] ? height[left]:height[right]);
            if(temp > res)
                res = temp;
            
            if(height[left] > height[right])
                right--;
            else
                left++;
        }
        return res;
    }
};

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

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

示例 1:

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

示例 2:

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

示例 3:

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

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int n = s.length();
        if(n < 2)
            return n;
        int num[256] = {0};
        int left = -1, right = -1, res = 0;
        
        while(right < (n-1))
        {
            if(num[s[right+1]] == 0)
            {
                ++right;
                num[s[right]]++;
            }
            else
            {
                res = max(res, right-left);
                ++left;
                num[s[left]]--;
            }
        }
        res = max(res, right-left);
        return res;
    }
};

438. 找到字符串中所有字母异位词

给定一个字符串 s 和一个非空字符串 p,找到 s 中所有是 p 的字母异位词的子串,返回这些子串的起始索引。

字符串只包含小写英文字母,并且字符串 s 和 p 的长度都不超过 20100。

说明:

    字母异位词指字母相同,但排列不同的字符串。
    不考虑答案输出的顺序。

示例 1:

输入:
s: "cbaebabacd" p: "abc"

输出:
[0, 6]

解释:
起始索引等于 0 的子串是 "cba", 它是 "abc" 的字母异位词。
起始索引等于 6 的子串是 "bac", 它是 "abc" 的字母异位词。

 示例 2:

输入:
s: "abab" p: "ab"

输出:
[0, 1, 2]

解释:
起始索引等于 0 的子串是 "ab", 它是 "ab" 的字母异位词。
起始索引等于 1 的子串是 "ba", 它是 "ab" 的字母异位词。
起始索引等于 2 的子串是 "ab", 它是 "ab" 的字母异位词。

(1)第一种方法

维护一个长度为字符串p的窗口, 每次向右移动一个单位。同时每次都要比较两者的内容是否相等,时间复杂度O(26n)

class Solution {
public:
    vector<int> findAnagrams(string s, string p) {
        int m = s.length();
        int n = p.length();
        vector<int>index;
        if(n > m)
            return index;
        
        vector<int>nump(26, 0);
        vector<int>nums(26, 0);
        for(int i = 0; i < n; i++)
        {
            nump[p[i]-'a']++;
            nums[s[i]-'a']++;
        }
        
        int left = 0, right = n;
        while(right < m)
        {
            if(nump == nums)
                index.push_back(left);
            nums[s[left]-'a']--;
            nums[s[right]-'a']++;
            left++;
            right++;
        }
        if(nump == nums)
                index.push_back(left);
        return index;
    }
    
};

(2)第二种方法

双指针滑动窗口,加上一个计数器len,时间复杂度O(n)

class Solution {
public:
    vector<int> findAnagrams(string s, string p) {
        int m = s.length();
        int n = p.length();
        vector<int>index;
        if(n > m)
            return index;
        
        vector<int>count(26, 0);
        for(int i = 0; i < n; i++)
            count[p[i]-'a']++;
        
        int left = 0, right = 0;
        int len = 0;
        while(right < m)
        {
            count[s[right]-'a']--;
            if(count[s[right]-'a'] >= 0)
                len++;
            right++;
            while(len == n)
            {
                if(right - left == n)
                    index.push_back(left);
                count[s[left]-'a']++;
                if(count[s[left]-'a'] > 0)
                    len--;
                left++;
            }
            
        }

        return index;
    }
    
};

76. 最小覆盖子串

给你一个字符串 S、一个字符串 T,请在字符串 S 里面找出:包含 T 所有字母的最小子串。

示例:

输入: S = "ADOBECODEBANC", T = "ABC"
输出: "BANC"

说明:

    如果 S 中不存这样的子串,则返回空字符串 ""。
    如果 S 中存在这样的子串,我们保证它是唯一的答案。

此题和上面一题类似,双指针维护一个窗口,只是窗口大小不固定,用一个计数器len来确定窗口是否含有指定的字符串。

class Solution {
public:
    string minWindow(string s, string t) {
        int m = s.length();
        int n = t.length();
        string res = "";
        if(n > m)
            return res;
        int count[256] = { 0 };
        for(int i =0; i < n; i++)
            count[t[i]]++;
        
        int left = 0, right = 0, len = 0, minlength = INT_MAX;
        while(right < m)
        {
            count[s[right]]--;
            if(count[s[right]] >= 0)
                len++;
            right++;
            while(len == n)
            {
                if(minlength > (right - left))
                {
                    minlength = right - left;
                    res = s.substr(left, right - left);
                }
                count[s[left]]++;
                if(count[s[left]] > 0)
                    len--;
                left++;
            }
        }
        
        return res;
    }
};

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值