内容简介:面试中的算法问题,有很多并不需要复杂的数据结构支撑。就是用数组,就能考察出很多东西了。其实,经典的排序问题,二分搜索等等问题,就是在数组这种最基础的结构中处理问题的,今天主要学习常见的数组中处理问题的方法。** 循环不变量。声明不变。控制边界。**** 极端情况:如果都为非0,则每个都自己和自己交换**
面试中的算法问题,有很多并不需要复杂的数据结构支撑。就是用数组,就能考察出很多东西了。其实,经典的 排序 问题,二分搜索等等问题,就是在数组这种最基础的结构中处理问题的,今天主要学习常见的数组中处理问题的方法。
数组中的问题其实最常见。
- 排序:选择排序;插入排序;归并排序;快速排序
- 查找:二分查找法
- 数据结构:栈;队列;堆
从二分查找法看如何写出正确的程序
- 建立一个基础的框架
- 什么是正确的程序
二分查找法
- 二分查找法的思想在1946年提出。
- 第一个没有bug的二分查找法在1962年才出现。
- 对于有序数列,才能使用二分查找法 (排序的作用)
需要注意的问题
- 声明变量的时候,明确变量的意义,并且在书写整个逻辑的时候,要不听的维护住这个变量的意义。
- 初始值问题
- 边界问题
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; } 复制代码
** 循环不变量。声明不变。控制边界。**
int l = 0, r = n-1; // 在[l...r]的范围里寻找target:前闭后闭 复制代码
改变变量定义,依然可以写出正确的算法
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; } 复制代码
注意
- 求mid值是采用(l+r)/2容易整形溢出
- 采用mid = l + (r-l)/2;
如何写出正确的程序?
- 明确变量的含义
- 循环不变量
- 小数据量调试(4到6个数据)空集,边界(小数据集上代码如何运作的)
- 耐心找到bug,定位错误发生的位置。
- 大数据量测试(性能)
LeetCode题解: Move Zeros
直观的解题思路
- 拿出非0元素
- 将非0元素拿出来,然后空位补0
class Solution { public: // 时间复杂度 O(n) // 空间复杂度 O(n) 新创建数组 void moveZeroes(vector<int>& nums) { vector<int> nonZeroElements; // 将vec中所有非0元素放入nonZeroElements中 for( int i = 0 ; i < nums.size() ; i ++ ) if( nums[i] ) nonZeroElements.push_back( nums[i] ); // 将nonZeroElements中的所有元素依次放入到nums开始的位置 for( int i = 0 ; i < nonZeroElements.size() ; i ++ ) nums[i] = nonZeroElements[i]; // 将nums剩余的位置放置为0 for( int i = nonZeroElements.size() ; i < nums.size() ; i ++ ) nums[i] = 0; } }; int main() { int arr[] = {0, 1, 0, 3, 12}; //根据生成的数据创建vector:传入头指针和尾指针 vector<int> vec(arr, arr + sizeof(arr)/sizeof(int)); Solution().moveZeroes(vec); for( int i = 0 ; i < vec.size() ; i ++ ) cout<<vec[i]<<" "; cout<<endl; return 0; } 复制代码
即使简单的算法也能进一步优化。
- 不开辟额外空间
- k - [0…k)中保存所有当前遍历过的非0元素
class Solution { public: // 时间复杂度 O(n) // 空间复杂度 O(1) void moveZeroes(vector<int>& nums) { int k = 0; // nums中, [0...k)的元素均为非0元素 // 遍历到第i个元素后,保证[0...i]中所有非0元素 // 都按照顺序排列在[0...k)中 for(int i = 0 ; i < nums.size() ; i ++ ) if( nums[i] ) nums[k++] = nums[i]; // 将nums剩余的位置放置为0 for( int i = k ; i < nums.size() ; i ++ ) nums[i] = 0; } }; int main() { int arr[] = {0, 1, 0, 3, 12}; vector<int> vec(arr, arr + sizeof(arr)/sizeof(int)); Solution().moveZeroes(vec); for( int i = 0 ; i < vec.size() ; i ++ ) cout<<vec[i]<<" "; cout<<endl; return 0; } 复制代码
进一步优化
-
非0的赋值不用操作了。
-
非0的与0直接互换。
class Solution { public: // 时间复杂度 O(n) // 空间复杂度 O(1) void moveZeroes(vector<int>& nums) { int k = 0; // nums中, [0...k)的元素均为非0元素 // 遍历到第i个元素后,保证[0...i]中所有非0元素 // 都按照顺序排列在[0...k)中 // 同时, [k...i] 为0 for(int i = 0 ; i < nums.size() ; i ++ ) if( nums[i] ) swap( nums[k++] , nums[i] ); } }; 复制代码
** 极端情况:如果都为非0,则每个都自己和自己交换**
class Solution { public: // 时间复杂度 O(n) // 空间复杂度 O(1) void moveZeroes(vector<int>& nums) { int k = 0; // nums中, [0...k)的元素均为非0元素 // 遍历到第i个元素后,保证[0...i]中所有非0元素 // 都按照顺序排列在[0...k)中 // 同时, [k...i] 为0 for(int i = 0 ; i < nums.size() ; i ++ ) if( nums[i] ) // if( k != i ) swap( nums[k++] , nums[i] ); else// i == k k ++; } }; 复制代码
相似题目
- leetcode 27
- leetcode 26
- leetcode 80
注意的问题
- 如何定义删除?从数组中去除?还是放在数组末尾?
- 剩余元素的排列是否要保证原有的相对顺序?
- 是否有空间复杂度的要求? O(1)
基础算法思路的应用
75 Sort Colors
基数排序法
// 时间复杂度: O(n) // 空间复杂度: O(k), k为元素的取值范围 // 对整个数组遍历了两遍 class Solution { public: void sortColors(vector<int> &nums) { int count[3] = {0}; // 存放0,1,2三个元素的频率 for( int i = 0 ; i < nums.size() ; i ++ ){ assert( nums[i] >= 0 && nums[i] <= 2 ); count[nums[i]] ++; } int index = 0; for( int i = 0 ; i < count[0] ; i ++ ) nums[index++] = 0; for( int i = 0 ; i < count[1] ; i ++ ) nums[index++] = 1; for( int i = 0 ; i < count[2] ; i ++ ) nums[index++] = 2; // 小练习: 更加自使用的计数排序 } }; int main() { int nums[] = {2, 2, 2, 1, 1, 0}; vector<int> vec = vector<int>( nums , nums + sizeof(nums)/sizeof(int)); Solution().sortColors( vec ); for( int i = 0 ; i < vec.size() ; i ++ ) cout<<vec[i]<<" "; cout<<endl; return 0; } 复制代码
可以只扫描一遍么?
一次三路快排
设置三个索引:zero two i
三路快排
// 时间复杂度: O(n) // 空间复杂度: O(1) // 对整个数组只遍历了一遍 class Solution { public: void sortColors(vector<int> &nums) { int zero = -1; // [0...zero] == 0 int two = nums.size(); // [two...n-1] == 2 for( int i = 0 ; i < two ; ){ if( nums[i] == 1 ) i ++; else if ( nums[i] == 2 ) swap( nums[i] , nums[--two]); else{ // nums[i] == 0 assert( nums[i] == 0 ); swap( nums[++zero] , nums[i++] ); } } } }; int main() { int nums[] = {2, 2, 2, 1, 1, 0}; vector<int> vec = vector<int>( nums , nums + sizeof(nums)/sizeof(int)); Solution().sortColors( vec ); for( int i = 0 ; i < vec.size() ; i ++ ) cout<<vec[i]<<" "; cout<<endl; return 0; } 复制代码
相似题目
- 88 Merge Sorted Array
- 215 Kth Largest Element in an Array
双索引技术-对撞指针
167 两数之和 II - 输入有序数组
需要考虑的问题
- 如果没有解怎样?保证有解
- 如果有多个解怎样?返回任意解
解法
-
最直接的思考:暴力解法。双层遍历,O(n^2)
- 暴力解法没有充分利用原数组的性质 —— 有序:有序?二分搜索?
-
二分搜索法
- 对于每个i, 在剩余数组中查找target-nums[i]的值
- 时间复杂度为O(NlogN)
-
对撞指针
- 一般会是大于或者小于。
- 如果大i++ 小 j--
- 两个索引在往中间走。对撞指针。
代码实现
// 时间复杂度: O(n) // 空间复杂度: O(1) class Solution { public: vector<int> twoSum(vector<int>& numbers, int target) { assert( numbers.size() >= 2 ); // assert( isSorted(numbers) ); int l = 0, r = numbers.size()-1; while( l < r ){ if( numbers[l] + numbers[r] == target ){ int res[2] = {l+1, r+1}; return vector<int>(res, res+2); } else if( numbers[l] + numbers[r] < target ) l ++; else // numbers[l] + numbers[r] > target r --; } throw invalid_argument("the input has no solution"); } }; 复制代码
相似题目
- 125 Valid Palindrome
- 空字符串如何看?
- 字符的定义?
- 大小写问题
- 344 Reverse String
- 345 Reverse Vowels of a String
- 11 Container With Most Water
双索引技术-滑动窗口
209长度最小的子数组
什么是子数组
- 一般不要求连续
- 而这个题目中规定了子数组要连续这样的特性。
- 如果没有解怎么办?返回0
暴力解O(N^3)
- 计算其和sum,验证sum >= s
- 时间复杂度O(n^3)
代码实现
int minSubArrayLen(int s, vector<int>& nums) { assert(s > 0); int res = nums.size() + 1; for(int l = 0 ; l < nums.size() ; l ++) for(int r = l ; r < nums.size() ; r ++){ int sum = 0; for(int i = l ; i <= r ; i ++) sum += nums[i]; if(sum >= s) res = min(res, r - l + 1); } if(res == nums.size() + 1) return 0; return res; } 复制代码
暴力解的优化O(N^2)
int minSubArrayLen(int s, vector<int>& nums) { assert(s > 0); // sums[i]存放nums[0...i-1]的和 vector<int> sums(nums.size() + 1, 0); for(int i = 1 ; i <= nums.size() ; i ++) sums[i] = sums[i-1] + nums[i-1]; int res = nums.size() + 1; for(int l = 0 ; l < nums.size() ; l ++) for(int r = l ; r < nums.size() ; r ++){ // 使用sums[r+1] - sums[l] 快速获得nums[l...r]的和 if(sums[r+1] - sums[l] >= s) res = min(res, r - l + 1); } if(res == nums.size() + 1) return 0; return res; } 复制代码
滑动窗口解
-
如果当前子数组不到就往后再看一个
-
窗口不停向前滑动。
// 滑动窗口的思路 // 时间复杂度: O(n) // 空间复杂度: O(1) class Solution { public: int minSubArrayLen(int s, vector<int>& nums) { //nums[l...r]为我们的滑动窗口 int l = 0, r = -1; int sum = 0; int res = nums.size() + 1; while(l < nums.size()){ if(r+1 < nums.size() && sum < s){ r++; sum += nums[r]; }else{ sum -= nums[l]; l++; } if(sum >= s){ res = min(res, r - l + 1); } } if(res == nums.size() + 1) return 0; return res; } }; 复制代码
在滑动窗口中做记录
无重复字符的最长子串
注意
字符集?只有字母?数字+字母?ASCII?
大小写是否敏感?
思路
- j++如果没有重复元素,窗口j继续往后
- 如果有重复元素,i++去除重复
- freq[256]记录窗口中的元素
实现代码
class Solution { public: int lengthOfLongestSubstring(string s) { int freq[256] = {0}; int l = 0, r = -1; //滑动窗口为s[l...r] int res = 0; // 整个循环从 l == 0; r == -1 这个空窗口开始 // 到l == s.size(); r == s.size()-1 这个空窗口截止 // 在每次循环里逐渐改变窗口, 维护freq, 并记录当前窗口中是否找到了一个新的最优值 while( l < s.size() ){ if( r + 1 < s.size() && freq[s[r+1]] == 0 ) freq[s[++r]] ++; else //r已经到头 || freq[s[r+1]] == 1 freq[s[l++]] --; res = max( res , r-l+1); } return res; } }; int main() { cout << Solution().lengthOfLongestSubstring( "abcabcbb" )<<endl; cout << Solution().lengthOfLongestSubstring( "bbbbb" )<<endl; cout << Solution().lengthOfLongestSubstring( "pwwkew" )<<endl; cout << Solution().lengthOfLongestSubstring( "" )<<endl; return 0; } 复制代码
相似题目
-
438 Find All Anagrams in a String
- 字符集范围?英文小写字母
- 返回的解的顺序?任意。
-
76 Minimum Window Substring
- 字符集范围
- 若没有解? 返回“”
*若有多个解?保证只有一个解 - 什么叫包含所有字符?S = “a”,T = “aa”
-------------------------华丽的分割线--------------------
看完的朋友可以点个喜欢/关注,您的支持是对我最大的鼓励。
个人博客番茄技术小栈和 掘金主页
想了解更多,欢迎关注我的微信公众号:番茄技术小栈
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- Leetcode 题解|26. 删除排序数组中的重复项
- [剑指offer题解][Java]数组中出现次数超过一半的数字
- leetcode题解(动态规划)
- leetcode题解(动态规划)
- Leetcode 565 & 240 题解
- 卖酒的算法题解
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Probability and Computing
Michael Mitzenmacher、Eli Upfal / Cambridge University Press / 2005-01-31 / USD 66.00
Assuming only an elementary background in discrete mathematics, this textbook is an excellent introduction to the probabilistic techniques and paradigms used in the development of probabilistic algori......一起来看看 《Probability and Computing》 这本书的介绍吧!