算法笔记_12.8-12.15

1. 两数之和

给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。

你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。

示例:
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]

解法

  • 暴力法
    对vector进行嵌套的两遍for循环
    简单但是时间复杂度为O(n2),空间复杂度为O(1);运行速度慢
  • 两遍哈希表
    用map实现(每个关键字只能在map中出现一次,第二个可能称为该关键字的值)
    第一遍将vector中的数据(作为关键字key)和数据的下标(作为值value),存入map
    第二遍在map中寻找目标元素
    时间复杂度为O(n)
    空间复杂度为O(n)
class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        map<int,int> int_map;
        vector<int> ans(2, -1);
        for(int i=0; i<nums.size(); ++i){
            int_map.insert(map<int,int>::value_type(nums[i],i));
        }
        
        for(int i=0; i<nums.size(); ++i){
            if(int_map.count(target - nums[i]) > 0 && (int_map[target -nums[i]] != i)){
                ans[0] = i;
                ans[1] = int_map[target - nums[i]];
                break;
        }      
        return ans;
    }
};

  • 一遍哈希表
    在进行迭代并将元素插入到表中的同时,检查表中是否已经存在当前元素所对应的目标元素。如果它存在,则立即将其返回。
    时间复杂度为O(n)
    空间复杂度为O(n)
class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        map<int,int> int_map;
        vector<int> ans(2, -1);
        for(int i=0; i<nums.size(); ++i){
            int_map.insert(map<int,int>::value_type(nums[i],i));
            if(int_map.count(target - nums[i]) > 0 && (int_map[target -nums[i]] != i)){
                ans[0] = i;
                ans[1] = int_map[target - nums[i]];
                break;
            }
        }      
        return ans;
    }
};



2.整数反转

给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转。

示例 1:
输入: 123
输出: 321

示例 2:
输入: -123
输出: -321

示例 3:
输入: 120
输出: 21

注意:

假设我们的环境只能存储得下 32 位的有符号整数,则其数值范围为 [−231, 231 − 1]。请根据这个假设,如果反转后整数溢出那么就返回 0.

解法

  • 数学法
    在没有辅助堆栈 / 数组的帮助下 “弹出” 和 “推入” 数字,可以使用数学方法。
    C++中INT_MAX代表最大整数231 − 1 = 2147483647
    INT_MIN代表最小整数−231 = -2147483648
//pop operation:
pop = x % 10;
x /= 10;
//push operation:
temp = rev * 10 + pop;
rev = temp;
class Solution {
public:
    int reverse(int x) {
       long ans=0; //利用long型整数储存结果防止溢出

       while(x!=0){
           ans *= 10;       
           ans += x % 10;
           x = x /10;
       } 

       if((ans >= INT_MIN) && (ans <= INT_MAX))
            return ans;
       else
            return 0;
    }
};

复杂度分析
时间复杂度:O(log(x)),x 中大约有 log10(x) 位数字。
空间复杂度:O(1)。


3.回文数

解法同上,可只反转一半就进行判断


4.罗马数字转整数

解法

  • if法
    for循环中用if写出所有条件
    时间复杂度为O(n)
    空间复杂度为O(1)

  • map法
    用map储存罗马数字和代表的值组成的(key,value),循环时将IV分解为1(‘I’)+3(“IV”)=4
    时间复杂度为O(n)
    空间复杂度为O(n)


5.移动零

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

示例:
输入: [0,1,0,3,12]
输出: [1,3,12,0,0]

说明:
必须在原数组上操作,不能拷贝额外的数组。
尽量减少操作次数。

解法

  • 冒泡法
class Solution {
public:   
void moveZeroes(vector<int>& nums) {       
	for(int j = 1; j<nums.size(); ++j){           
		for(int i = 1; i<nums.size(); ++i) {
                	if(nums[i-1] == 0){
                        	int tmp = nums[i];
                        	nums[i] = nums[i-1];
                        	nums[i-1] = tmp;
                	}           
		}        
	}
}
};


复杂度分析

时间复杂度:O(n2),嵌套的两次遍历。

空间复杂度:O(1)。

  • 双指针法
    通过一快(curr_ptr)一慢两个指针,如果快指针遇到非零元素直接,将非零元素复制到慢指针处,然后将快指针(已处理过)处元素赋值为0。

    循环不变量为 
    1.慢指针(lastnonzerofoundat)之前的所有元素都是非零的。
    2.当前指针和慢速指针之间的所有元素都是零。
    
class Solution {
public:
    void moveZeroes(vector<int>& nums) {           //双指针法
    int slow_ptr = 0;   //nums[0,slow_ptr]内均为非0元素
    int tmp;
    for(int curr_ptr = 0; curr_ptr<nums.size(); ++curr_ptr){
        if(nums[curr_ptr] != 0){
            tmp = nums[curr_ptr];
            nums[curr_ptr] = 0; //nums(slow_ptr,curr_ptr]内均为0
            nums[slow_ptr] = tmp;
            ++slow_ptr;
        }
    }
    }
};

复杂度分析

时间复杂度:O(n)

空间复杂度:O(1)


6.移除元素

要求空间复杂度为0

解法

  • 双指针法
    思路同上(移动零),将不为val(要移除的元素的值)的元素换到前面

  • 双指针 —— 当要删除的元素很少时
    只需要将要删除的元素与数组尾部元素(新长度后不需要的元素)交换
    此方法会改变数组顺序

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int end_ptr = nums.size() - 1;      //指向最后一个元素
        int curr_ptr = 0;
        while(curr_ptr <= end_ptr){
            if(nums[curr_ptr] == val){
                nums[curr_ptr] = nums[end_ptr--];  
                //用数组尾部元素替换需删除元素
            }
            else
                ++curr_ptr;
        }
        
    return (++end_ptr); //返回新数组长度
    }
};

复杂度分析

均为
时间复杂度:O(n)

空间复杂度:O(1)


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

要求空间复杂度为0

解法

  • 双指针法
    思路同上(移动零),利用快慢两个指针来分别扫描(快指针)记录新数组(慢数组)
class Solution {
public:   
int removeDuplicates(vector<int>& nums) {       	
	int slow_ptr = 0, curr_ptr = 1;         
	//slow_ptr指向不重复数组的末尾,curr_ptr指向当前扫描到的元素       
	int size = nums.size();     //储存不重复数组的大小
       
	while(curr_ptr<nums.size()){           
		if(nums[slow_ptr] == nums[curr_ptr]){
                	++curr_ptr;
                	--size;           
		}           
		else{
                	nums[++slow_ptr] =nums[curr_ptr++];           
		}       
	}       
	return size;
}
}; 

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


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

给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素最多出现两次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。

示例
给定 nums = [1,1,1,2,2,3],
函数应返回新长度 length = 5, 并且原数组的前五个元素被修改为 1, 1, 2, 2, 3 。
你不需要考虑数组中超出新长度后面的元素。

解法

  • 快慢指针
    1.慢指针指向将要填充的位置
    2.指针向后逐个遍历
class Solution {
public:   
int removeDuplicates(vector<int>& nums) {       
if (nums.empty()) return 0;       
int k = 1;       
int c = 1;
       
for (int i = 1; i < nums.size(); ++i) {           
if (nums[i] == nums[i - 1]) {
                ++c;           
} else {
                c = 1;           
}           
if (c <= 2) {
                nums[k++] = nums[i];           
}       
}       
return k;
}
};

复杂度分析

时间复杂度:O(n)

空间复杂度:O(1)


9.颜色分类

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

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

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

解法

  • 计数排序

使用计数排序的两趟扫描算法。

首先,迭代计算出0、1 和 2 元素的个数,然后按照0、1、2的排序,重写当前数组。

class Solution {
public:   
void sortColors(vector<int>& nums) {       
int i0 = 0, i1 = 0, i2 = 0;//当排序数组的元素类型较少时,可使用s选择排序       
for(auto i : nums){           
if(i == 0) ++i0;           
if(i == 1) ++i1;          
if(i == 2) ++i2;       
}       
for(int i = 0; i < i0; ++i){nums[i] = 0;}       
for(int i = i0; i < i0+i1; ++i){nums[i] = 1;}       
for(int i = i0+i1; i < i0+i1+i2; ++i){nums[i] = 2;}  

}
};

复杂度分析

时间复杂度:O(n)

空间复杂度:O(n)

  • 三路快排
    我们用三个指针(p0, p2 和curr)来分别追踪0的最右边界,2的最左边界和当前考虑的元素。
    本解法的思路是沿着数组移动 curr 指针,若nums[curr] = 0,则将其与 nums[p0]互换;若 nums[curr] = 2 ,则与 nums[p2]互换。
class Solution {
public:
    void sortColors(vector<int>& nums) {
        int i_low = 0, i_high = nums.size();
        //三路快排思想,[0,low)之间存放0、[low,curr)之间存放1、[high,num.size())之间存放2
        //循环不变量要确保初始、循环中、结束后都不变
        for(int i_curr =0; i_curr < i_high; ++i_curr){
            if(nums[i_curr] == 0){
                swap(nums[i_low++],nums[i_curr]);
            }
            //if(nums[i_curr] == 1) ++i_curr;
            if(nums[i_curr] == 2){
                swap(nums[--i_high],nums[i_curr--]);
            }
        }
    }
};

复杂度分析

时间复杂度:O(n)

空间复杂度:O(1)
且只扫描了一遍数组


10.合并两个有序数组

说明:

初始化 nums1 和 nums2 的元素数量分别为 m 和 n。

你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。

   示例:
输入:
nums1 = [1,2,3,0,0,0], m = 3
nums2 = [2,5,6],       n = 3
输出:
[1,2,2,3,5,6]

解法

  • 双指针法(从前往后)
    使用两个指针分别扫描两个数组,将两数组当前扫描元素中较小的放入新数组里
    然后把新数组的内容拷贝进原数组num1
class Solution {
public:
    void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
        vector <int> res_vec;
        int i_m = 0, i_n = 0;
        while((i_m<m) && (i_n<n)){
            if(nums1[i_m]<nums2[i_n]){
                res_vec.push_back(nums1[i_m++]);
            }
            else{
                res_vec.push_back(nums2[i_n++]);
            }
        } 
        while(i_m < m) res_vec.push_back(nums1[i_m++]);
        while(i_n < n) res_vec.push_back(nums2[i_n++]); 

        for(int i = 0; i < res_vec.size(); ++i) nums1[i]=res_vec[i];
    }
};

复杂度分析

时间复杂度:O(n),

空间复杂度:O(n),因为需要开辟新数组存放扫描完成的元素。
如果采用双指针法(从后往前,从大到小扫描)将扫描完成的元素放进num1原数组的后面就可将空间复杂度降为O(1)。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值