首页 > 其他 > 详细

Leetcode题目解析

时间:2021-04-27 22:05:44      阅读:23      评论:0      收藏:0      [点我收藏+]

数组

1. 989-数组形式的整数加法

题目:

对于非负整数 X 而言,X 的数组形式是每位数字按从左到右的顺序形成的数组。例如,如果 X = 1231,那么其数组形式为 [1,2,3,1]。

示例:
输入:A = [1,2,0,0], K = 34
输出:[1,2,3,4]
解释:1200 + 34 = 1234

例程:

class Solution {
public:
    vector<int> addToArrayForm(vector<int>& A, int K) {
        vector<int> list;
        
        for(int i = A.size()-1; i >= 0; i--){
            int r = A[i] + K;
            list.push_back(r % 10);
            K = r / 10;
        }
        for(;K;K /= 10) list.push_back(K%10);
        reverse(list.begin(),list.end());
        return list;
    }
};

解释:

  1. K先与个位相加,与10取余后得到结果的个位数;

    K整除10,舍弃个位,进行十位计算;

    循环i次(即使过程中K为0,也不影响计算)

  2. 最后再附上K的单循环,防止K过大,为计算完。

2. 31-下一个队列

题目:

实现获取下一个队列的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。

如果不存在下一个更大的排列,则将数字重新排列成最小的排列。(即升序排列)

示例:
输入:arr1 = [1,2,3]
输出:[1,3,2]

方法一:两遍扫描

程序:

C

void swap(int *a, int *b) {	//互换函数(指针思想)
    int t = *a;
    *a = *b, *b = t;
}
void reverse(int *nums, int left, int right) {	//区间倒序
    while (left < right) {
        swap(nums + left, nums + right);
        left++, right--;
    }
}

void nextPermutation(int *nums, int numsSize) {
    int i = numsSize - 2;	//指代倒数第二个数据
    while (i >= 0 && nums[i] >= nums[i + 1]) {
        i--;	//找到“较小数”
    }
    if (i >= 0) {	//小于0则表示整个序列已经都是倒序状态
        int j = numsSize - 1;	//指代最后一个数据
        while (j >= 0 && nums[i] >= nums[j]) {
            j--;	//找到“较大数”
        }
        swap(nums + i, nums + j);	//先将“较小数”和“较大数”交换
    }
    reverse(nums, i + 1, numsSize - 1);	//将“i+1”到最后一个数之间的区间进行倒序
}

C++

class Solution {
public:
    void nextPermutation(vector<int>& nums) {
        int i = nums.size() - 2;
        while (i >= 0 && nums[i] >= nums[i + 1]) {
            i--;
        }
        if (i >= 0) {
            int j = nums.size() - 1;
            while (j >= 0 && nums[i] >= nums[j]) {
                j--;
            }
            swap(nums[i], nums[j]);
        }
        reverse(nums.begin() + i + 1, nums.end());
    }
};
解释:
  1. 先写好区间倒序函数reverse及其子函数swap(如果采用C++编程,则reverse和swap函数可直接调用);
  2. 该方法关键在于找到“较小数”和“较大数”,分别指:
    1. 较小数:从右向左轮询,找到第一对非倒序数对,则前者为需要替换的较小数;
    2. 较大数:从右向较小数轮询,找到第一个大于较小数的数,即为较大数;
  3. 将较小数和较大数交换;
  4. 将较小数后的数到最后一个数进行反转;

3. 1122-数组的排序

题目:

给定两个数组,arr1和arr2

  • arr2中的元素各不相同
  • arr2中的每个元素都出现在arr1中

对arr1中的元素进行排序,使arr1中项的相对顺序和arr2中的相对顺序相同。未在arr2中出现过的元素需要按照升序放在arr1的末端。

示例:
输入:arr1 = [2,3,1,3,2,4,6,7,9,2,19], arr2 = [2,1,4,3,9,6]
输出:[2,2,2,1,4,3,3,9,6,7,19]

例程:

int* relativeSortArray(int* arr1, int arr1Size, int* arr2, int arr2Size, int* returnSize){
    int nums[1001] = {0};
    int *res = (int*)malloc(sizeof(int) * arr1Size);
    int i;
    int j = 0;
    /* 统计数字出现的次数 */
    for (i = 0; i < arr1Size; i++) {
        nums[arr1[i]]++;
    }
    /* 先取出arr2中出现的数字 */
    for (i = 0; i < arr2Size; i++) {
        while (nums[arr2[i]] > 0) {
            res[j++] = arr2[i];
            nums[arr2[i]]--;
        }
    }
    /* 将剩余数字升序排列 */
    for (i = 0; i < 1001; i++) {
        while (nums[i] != 0) {
            res[j++] = i;
            nums[i]--;
        }
    }
    *returnSize = j;
    return res;
}

C++

vector<int> SortArray(vector<int> arr1, vector<int> arr2) {
	unordered_map<int,int> hashtable;
	vector<int> res;
	int j = 0;
	for (int i = 0; i < arr1.size(); i++){
		hashtable[arr1[i]]++;
	}

	for (int i = 0; i < arr2.size(); i++) {
		while (hashtable[arr2[i]] > 0) {
			res.push_back(arr2[i]); //这里必须用push_back
			hashtable[arr2[i]]--;
		}
	}

	for (int i = 0; i < 1001; i++) {
		while (hashtable[i] != 0) {
			res.push_back(i);
			hashtable[i]--;
		}
	}
	return res;
}

解释:

  1. 统计arr1中数字出现的次数(nums数组的值为arr1数值出现次数,nums数组编号才是arr1数值);
  2. 取出arr2中数组的数
  3. 将arr1中其他数值进行排序(nums数值顺序即为升序顺序!)

链表

1. 21-合并两个有序链表

题目:

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

示例:
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]

技术分享图片

例程:

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        if (l1 == nullptr) {
            return l2;
        } else if (l2 == nullptr) {
            return l1;
        } else if (l1->val < l2->val) {
            l1->next = mergeTwoLists(l1->next, l2);
            return l1;
        } else {
            l2->next = mergeTwoLists(l1, l2->next);
            return l2;
        }
    }
};

解释:

  1. l1 = l1.next是指链表元素的指针,该句为指针的赋值;
  2. 思路
    1. 从各个链表的第一个元素开始比较,若l2>l1,则结果链表Curr指向l1,否则指向l2;
    2. 结果链表更新后,再将l1/l2链表指针后移一个;
    3. 结果链表指向最后一个元素时,因为已经跳出l1/l2不等于NULL的循环,所以进行选择赋值;
    4. 最后返回head.next(curr只是一个临时指针,最后要将指针指向第一个元素的head

2. 24-两两交换链表中的节点

题目:

给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。(不是单纯改变链表内部的值,而是需要实际的进行节点交换。)

示例:
输入:head = [1,2,3,4]
输出:[2,1,4,3]

技术分享图片

例程:

struct ListNode* swapPairs(struct ListNode* head){
    struct ListNode *p,*p1,*p2;
    int n = 0;
    p = head;
    //确保链表不是全空
    while (p != NULL){
        //链表只有奇数元素时break
        if(p->next == NULL)
            break;
        
        //元素二赋值给p1
        p1 = p->next;

        //如果是前两个元素,则p1为链表头
        if(n == 0){
            head = p1;
            n = 1;
        }
        //否则p1为第三个元素
        else
            p2->next = p1;
        
        //p p1 p2链表交换
        p->next = p1->next;
        p1->next = p;
        p2 = p;
        //p最终赋值为第三个元素
        p = p->next;
    }
    return head;
}

解释:

  1. 首先确保链表不是全空
  2. 接着排除链表只有一个元素的情况
  3. 设置三个中间指针向量
    • p赋初值head;
    • p1赋初值p->next;
      • 如果是第一次进判断,则p1赋值为head;
      • 如果不是,p1赋值为p2->next;(上一循环的第二个元素)
    • p1和p链表交换、p赋值给p2;
    • p步进一个元素(下一组的第一个元素,一组两个元素)

3. 141-环形链表

题目:

给定一个链表,判断链表中是否有环。

如果链表中有某个节点,可以通过连续跟踪next指针再次到达,则链表中存在环。

为了表示给定链表中的环,我们使用整数pos来表示链表尾连接到链表中的位置(索引从0开始),如果pos是-1,则在该链表中没有环。(注:pos不作为参数进行传递,仅为了标识链表的实际情况)

如果链表中存在环,则返回true;否则,返回false。

示例:
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点

技术分享图片

例程:

bool hasCycle(struct ListNode *head) {
    struct ListNode *fast,*slow;
    fast = head;
    slow = head;
    while(fast != NULL && fast->next != NULL){
        fast = fast->next->next;
        slow = slow->next;
        if(slow == fast)
            return true;
    }
    return false;
}

解释:

上述程序应用了快慢链表的思想

fast链表每次步进两个元素

slow链表每次步进一个元素

有环的话fast链表会追上slow链表,进而两个链表元素相等,返回true;否则跳出循环,返回false。

还有程序应用hash set思想,还没看到。

1. 572-另一个树的子树

题目:

给定两个非空二叉树s和t,检验s中是否包含和t具有相同结构和节点值的子树。s的一个子树包括s的一个节点的所有子孙。s也可以看做它自身的一颗子树。

示例1:

给定的树s

![image-20210407222319181](C:\Users\Mr. Chen\AppData\Roaming\Typora\typora-user-images\image-20210407222319181.png)

给定的树t

![image-20210407222334245](C:\Users\Mr. Chen\Desktop\image-20210407222334245.png)

返回True

示例2:

给定的树s

![image-20210407222523524](C:\Users\Mr. Chen\AppData\Roaming\Typora\typora-user-images\image-20210407222523524.png)

给定的树t

![image-20210407222535611](C:\Users\Mr. Chen\AppData\Roaming\Typora\typora-user-images\image-20210407222535611.png)

返回False

方法一:深度优先搜索(Depth First Search,DFS)

例程

class Solution {
public:
    //基本的树检测函数
    bool check(TreeNode *s, TreeNode *t) {
        if (!s && !t) {
            return true;
        }
        if ((s && !t) || (!s && t) || (s->val != t->val)) {
            return false;
        }
        return check(s->left, t->left) && check(s->right, t->right);
    }

    //DFS逻辑函数
    bool dfs(TreeNode *s, TreeNode *t) {
        if (!s) {
            return false;
        }
        return check(s, t) || dfs(s->left, t) || dfs(s->right, t);
    }

    //调用DFS
    bool isSubtree(TreeNode *s, TreeNode *t) {
        return dfs(s, t);
    }
};

解释

使用DFS枚举s中的每一个子节点,判断这个点的子树和t是否相等。

方式是让两个指针一开始先指向该节点和t的根,然后同步移动两根指针来同步遍历这两颗树。

方法二:深度优先搜索序列上做串匹配

方法三:树哈希

2. 297-二叉树的序列化和反序列化

题目:

请设计一个算法来实现二叉树的序列化和反序列化。(不限序列化和反序列化的执行逻辑,只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构)

示例:
输入:root = [1,2,NULL,NULL,3,4,NULL,NULL,5,NULL,NULL]
输出:[1,2,NULL,NULL,3,4,NULL,NULL,5,NULL,NULL]

技术分享图片

程序:

class Codec {
public:

    // Encodes a tree to a single string.
    string serialize(TreeNode* root) {
        if(root == NULL)
            return "NULL,"; //检测到空树,返回NULL

        string res = to_string(root->val) + ",";   //不是空节点,则将元素转为字符串类型,并加上该节点元素
        res += serialize(root->left);    //左孩子递归
        res += serialize(root->right);   //左孩子递归结束,自下而上继续右孩子递归
        
        return res;
    }

    // Decodes your encoded data to tree.
    TreeNode* deserialize(string data) {
        std::stringstream ss(data); //队列相关可参考https://www.cnblogs.com/john1015/p/12956593.html
        std::string item;
        queue<string> q;
        while (std::getline(ss, item, ‘,‘)) //常用分隔","方式      
            q.push(item);   //至此得到完整的队列q(包含所有树数据)
        return helper(q);
    }
    TreeNode* helper(queue<string>& q)  //还原二叉树
    {
        string val = q.front(); //获取队列最前端元素
        q.pop(); //获取后即弹出该元素
        if (val == "NULL")
            return NULL;
        TreeNode* head = new TreeNode(stoi(val));   //stoi()为带范围检查的string转int的函数 参考https://blog.csdn.net/qq_33221533/article/details/82119031 atoi才是char转int
        head->left = helper(q);
        head->right = helper(q);
        return head;
    }
};

解释:

  1. DFS
    1. 检测空节点,返回NULL;
    2. 否则执行计算(to_string(val)+“,”);
    3. 递归左孩子;
    4. 递归右孩子。
  2. 队列
    1. getline(),用于分割符号“,”,push用于将分割出的元素推入队列(先进先出);
    2. q.front() 获取队列最前端元素,接着pop(),即弹出该元素;
    3. 接着使用DFS的思想进行还原二叉树:
      1. 如果val为“NULL”,则return NULL;
      2. 否则,对二叉树指针内容进行正常赋值(使用stoi(val),将字符串类型转为int)
      3. 递归左孩子;
      4. 递归右孩子。

3. 129-求根节点到叶节点数字之和

题目:

给一个二叉树的根节点root,树中每个节点都存放有一个0-9之间的数字,每条从根节点到叶节点的路径都代表一个数字:

例如,从根节点到叶节点的路径为1->2->3,表示数字123。

计算从根节点到叶节点生成的所有数字之和。

叶节点是指没有子节点的节点)

示例:
输入:root = [1,2,3]
输出:25
解释:12+13

技术分享图片

例程:

int dfs(struct TreeNode* root, int prevSum) {
    if (root == NULL) {	//空节点,则返回0
        return 0;	
    }
    int sum = prevSum * 10 + root->val;	//只要不是空节点,就累加
    if (root->left == NULL && root->right == NULL) {
        return sum;	//表明已经到达叶节点
    } else {
        return dfs(root->left, sum) + dfs(root->right, sum); //否则继续搜索
    }
}

int sumNumbers(struct TreeNode* root) {
    return dfs(root, 0);
}

解释:

深度优先搜索,带参数的累加(将过去累加值作为参数)

  1. dfs,首先判断是否为空节点,是则返回;否则经过计算,再进行递归(return原函数)
  2. 将之前的累加值设置为dfs的参数,方便处理累加值。

哈希

1. 1-两数之和

题目:

给定一个整数数组nums和一个整数目标值target,请在该数组中找出“和为目标值”的那两个整数,并返回它们的数组下标。(假定每种输入只会对应一个答案,但是数组中同一元素在答案里不能重复出现。)

示例:
输入:nums= [2,7,11,15], target = 9
输出:[0,1]
解释:nums[0]+nums[1]=9

程序:

1.暴力枚举法
int* twoSum(int* nums, int numsSize, int target, int* returnSize){
    for(int i = 0;i < numsSize-1;i++){
        for (int j = i+1;j<numsSize;j++){
            if (nums[i]+nums[j]==target){
                int* res = malloc(sizeof(int) * 2);
                res[0] = i;
                res[1] = j;
                *returnSize = 2;
                return res;
            }
        }
    }
    *returnSize = 0;
    return NULL;
}
解释:
  1. 第一个for循环确定第一个数;
  2. 第二个for循环确定第二个数,且该数是从第一个数后面开始遍历;
  3. 二者相加和为target则返回结果,否则返回NULL。
2. 哈希表
class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int, int> hashtable;
        for (int i = 0; i < nums.size(); ++i) {
            auto it = hashtable.find(target - nums[i]);
            if (it != hashtable.end()) {
                return {it->second, i};
            }
            hashtable[nums[i]] = i;//先找后加入表,防止查到自己(元素5,target=10)
        }
        return {};
    }
};
解释:
  1. 构建哈希表(unordered_map类型,第一个参数为键(索引)、第二个参数为值);
  2. 进入唯一的for循环;
  3. 查找哈希表中有无(target-nums[i]),有则返回对应矩阵;
    1. find(val) 查找以 val 为键值对,如果找到,则返回一个指向该键值对的正向迭代器(it->first为键,it-second为值);反之,则返回一个指向容器中最后一个键值对之后位置的迭代器,即 尾后迭代器end()
  4. 否则将将该元素值赋值给哈希表的序列号赋值给
  5. 如果一个for循环跑完了还没有,就return空。

1. 394-字符串解码

题目:

给定一个经过编码的字符串,返回它解码的字符串。

编码规则为:k[string],k表示string重复了k次,k为正整数。

示例:
输入:s = "3[a]2[bc]"
输出:"aaabcbc"
解释:3次a+2次bc

方法一:子函数+递归

程序
class Solution {
public:
    pair<string,int> solver(const string&s, int i){
        int multiplier = 0;
        string mul = "";
        string ans = "";
        while(i < s.length()){
            if(s[i] >= ‘0‘&&s[i] <= ‘9‘){ //检测到是数字,则将数字累加(字符串形式)
                mul = mul + s[i];//这里没有把之前的数据位清除
                //multiplier = multiplier*10 +s[i] - ‘0‘; //这里通过直接将字符串解算成int
            }else if (s[i] == ‘[‘){ //检测到左括号,进行递归,再检测,再递归,直至检测到‘]‘
                auto sub_ans = solver(s,i+1);
                i = sub_ans.second;
                multiplier = stoi(mul); //字符串类型转为int
                while(multiplier > 0){
                    ans += sub_ans.first; //字符串重复
                    multiplier--;
                }
                mul = ""; //清空mul
            }else if (s[i] == ‘]‘){	//检测到‘]‘,返回数据对
                return pair(ans,i);
            }else {	//检测到字母,则字符串ans++
                ans += s[i];
            }
            i++; //进行下一次检测
        }
        return pair(ans,i); //返回总的数据对
    }
    string decodeString(string s) {
        return solver(s,0).first;
    }
};
解释:
  1. 子函数solver,若i<字符串s长度,则:
    1. 检测到数字,则字符串累加
    2. 检测到‘[‘,进行递归,直至检测到‘]‘
      1. 递归
      2. stoi(mul),将字符串转为int
      3. 基于multiplier,进行字符串ans重复
    3. 检测到‘]‘,返回当前数据对pair
    4. 否则即检测到字母,进行字符串ans++
    5. i++,进行下一次检测
  2. i>字符串s长度,检测结束,返回最终pair数据对

方法二:栈操作

程序:
class Solution {
public:
    string getDigits(string &s, size_t &ptr) { //获取数字子函数
        string ret = "";
        while (isdigit(s[ptr])) {	//此处while循环是为了获取二位数及以上的倍数
            ret.push_back(s[ptr++]); //此处已经将ptr++
        }
        return ret;
    }

    string getString(vector <string> &v) {	//为了将string数组v中的字符串全部导出为单独的字符串
        string ret;
        for (const auto &s: v) {
            ret += s;
        }
        return ret;
    }

    string decodeString(string s) {
        vector <string> stk;
        size_t ptr = 0;

        while (ptr < s.size()) {
            char cur = s[ptr];
            if (isdigit(cur)) {
                // 获取一个数字并进栈
                string digits = getDigits(s, ptr); //该函数内已经将ptr++
                stk.push_back(digits);
            } else if (isalpha(cur) || cur == ‘[‘) {
                // 获取一个字母并进栈
                stk.push_back(string(1, s[ptr++])); 
            } else { //检测到右括号,开始出栈
                ++ptr;
                vector <string> sub; //子字符串数组
                while (stk.back() != "[") {
                    sub.push_back(stk.back());
                    stk.pop_back();
                }
                reverse(sub.begin(), sub.end());
                // 左括号出栈
                stk.pop_back();
                // 此时栈顶为当前 sub 对应的字符串应该出现的次数
                int repTime = stoi(stk.back()); 
                stk.pop_back();
                string t, o = getString(sub);
                // 构造字符串
                while (repTime--) t += o; 
                // 将构造好的字符串入栈
                stk.push_back(t);
            }
        }

        return getString(stk);
    }
};
解释:
  1. 主循环

    1. 判断元素是否为数字,是则入栈;
    2. 判断元素是否为字符/‘[‘,是则获取一个元素入栈;
    3. 否则元素即为‘]‘,开始出栈
      1. 重新定义子字符串数组;
      2. 自右向左依次将原栈最后一个元素入栈到新数组,紧接着出栈,依次循环,直至检测到‘[‘;
      3. 将栈内元素倒置,获得正序元素;
      4. 或者栈顶元素,即为倍数,并stoi转化为int类型
      5. 构建字符串,并将其入栈
    4. 返回第一步
  2. 子函数

    1. getdigits,获取倍数

      isdigit(char),判断char是否为数字

    2. getstring,将字符串类型数组转为字符串

      isalpha(char),判断char是否为字符串

2. 739-每日温度

题目:

根据每日气温列表,重新生成一个列表,对应位置的数据为“想要观测到更高的气温,至少需要等待的天数

如果该日气温后续不再上升,则对应位置数据置0

示例:
输入:temperatures = [73,74,75,71,69,72]
输出:[1,1,0,2,1,0]

方法:单调栈

程序:
class Solution {
public:
    vector<int> dailyTemperatures(vector<int>& T) {
        int n = T.size();
        vector<int> ans(n);
        stack<int> s;
        for (int i = 0; i < n; ++i) {
            while (!s.empty() && T[i] > T[s.top()]) { //栈非空,且当前数据大于栈顶数据
                int previousIndex = s.top();
                ans[previousIndex] = i - previousIndex;
                s.pop();
            }
            s.push(i);
        }
        return ans;
    }
};
解释:
  1. 遍历数组内元素,且维护一个单调栈
    1. 若栈非空,且当前元素值大于栈顶元素值,则计算天数差,并保存在结果数组ans中,同时弹出参与比较的元素;
    2. 否则将数组元素压入栈内,即为新的栈顶元素(栈为空或者当前元素小于栈顶元素的情况)
  2. 返回结果数组ans

1. 295-数据流的中位数

题目:

中位数是有序列表中间的数,如果列表长度是偶数,中位数是中间两个数的平均值。

设计一个支持以下两种操作的数据结构:

  • void addNum(int num) - 从数据流中添加一个整数到数据结构中;
  • double findMedian() - 返回目前所有元素的中位数
示例:
输入:addNum(1) addNum(2) findMedian() addNum(3) findMedian()
输出:[null, null, null, 1.5, null, 2.0]

方法一:暴力法

程序:
class MedianFinder {
    vector<double> store;

public:
    // Adds a number into the data structure.
    void addNum(int num)
    {
        store.push_back(num); //数组中加入元素
    }

    // Returns the median of current data stream
    double findMedian()
    {
        sort(store.begin(), store.end()); //将数组中数据按从小到大的顺序放置

        int n = store.size();
        return (n & 1 ? store[n / 2] : (store[n / 2 - 1] + store[n / 2]) * 0.5); //返回中位数
    }
};
解释:
  1. 设置存储数组store;
  2. addNum函数用push_back指令将数据压入数组中;
  3. 用sort函数将store数组中的元素按从小到大排列,并返回中位数。

但这样写会超时!!!

n&1指的是n的最低位与1想与,若为奇数,则返回1;若为偶数,则返回0.

方法二:插入排序

程序:
class MedianFinder {
    vector<int> store; // resize-able container

public:
    // Adds a number into the data structure.
    void addNum(int num)
    {
        if (store.empty())	//空矩阵则直接将数据存入
            store.push_back(num);
        else	//否则通过insert插入,insert的键值由lower_bound获取
            store.insert(lower_bound(store.begin(), store.end(), num), num);     // binary search and insertion combined
    }

    // Returns the median of current data stream
    double findMedian()
    {
        int n = store.size();	//接下来直接同暴力法相同
        return n & 1 ? store[n / 2] : (store[n / 2 - 1] + store[n / 2]) * 0.5;
    }
};
解释:

中心思想与暴力法一致,先获取排序好的数组store,然后再return中位数

  1. AddNum函数:

    1. 如果Store为空,则直接将数据压入矩阵;

    2. 否则通过insert插入数据,insert的键由lower_bound函数获取

      lower_bound(First,Last,num)指搜索(First,Last)之间第一个不小于num的数的键;

      insert(key,num)指在key处插入num

  2. findMedian()同暴力法,直接返回中位数

方法三:大顶堆和小顶堆(优先级队列)

程序:
class MedianFinder {
    priority_queue<int> lo;                              // max heap
    priority_queue<int, vector<int>, greater<int>> hi;   // min heap

public:
    // Adds a number into the data structure.
    void addNum(int num)
    {
        lo.push(num);                                    // Add to max heap

        hi.push(lo.top());                               // balancing step
        lo.pop();

        if (lo.size() < hi.size()) {                     // maintain size property
            lo.push(hi.top());
            hi.pop();
        }
    }

    // Returns the median of current data stream
    double findMedian()
    {
        return lo.size() > hi.size() ? (double) lo.top() : (lo.top() + hi.top()) * 0.5;
    }
};

解释:

通过构建平衡两个大顶堆小顶堆,来获取最中间的数字(一个/两个);

  1. 定义一个大顶堆和一个小顶堆,即“priority_queue”格式

    优先队列(priority_queue)用法:

    1. top 访问对头元素
    2. empty 队列是否为空
    3. size 返回队列内元素个数
    4. push 插入元素到队尾(并排序)
    5. emplace 原地构造一个元素并插入队列
    6. pop 弹出队头元素
    7. swap 交换内容

    定义格式:

    priority_queue<int, vector, greater>指小顶堆

    priority_queue<int, vector, less>指大顶堆(默认)

  2. AddNum函数

    1. 大顶堆压入一个数据,并将大顶堆顶端数据压入小顶堆,并弹出;
    2. 检测大顶堆数据是否大于等于,否则将小顶堆顶端数据压入大顶堆,并弹出;

    以上两步可以保证二者相互平衡或接近。

  3. findMedian函数

    判断大顶堆和小顶堆数据是否相等,来确定是单数数据还是双数,并return对应中位数。

双指针及二分

1. 15-三数之和

题目:

给一个包含n个整数的数组nums,判断nums中是否存在三个元素a,b,c,使得a+b+c=0?

请找出所有和为0且不重复的三元组。

示例1:
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
示例2:
输入:nums = []
输出:[]

方法:排序+双指针

程序
class Solution {
public:
    vector<vector<int>> twoSum(vector<int>& nums, int start, int end, int target, int value){
        vector<vector<int>> answer;
        while(start < end){
            int sum = nums[start] + nums[end];
            if(sum == target){ //找到目标和
                vector<int>result; //定义结果子数组
                result.push_back(value); //第一个数组元素
                result.push_back(nums[start]); //第二个数组元素
                result.push_back(nums[end]); //第三个数组元素
                answer.push_back(result); //压入结果数组
                while(start < end && nums[start] == nums[start+1]){ //防止重复,移动指针
                    start++;
                }
                start++; //继续搜索下一组
                while(start < end && nums[end] == nums[end-1]){ //防止重复,移动指针
                    end--;
                }
                end--; //继续搜索下一组
            }else if(sum < target){
                start++; //如果和小于目标值,则后移前向指针
            }else{
                end--; //如果和大于目标值,则前移后向指针
            }
        }
        return answer; //返回结果数组(仅对应一个解的数组)
    }
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> answer; //好家伙,原来是这样定义的
        sort(nums.begin(), nums.end()); //方便执行双指针,对数组进行排序
        int size = nums.size();
        for(int i = 0;i < size;i++){
            if(i>0&&nums[i] == nums[i-1]){
                continue; //如果两个数组元素相等,则跳过,不再执行twoSum
            }
            auto result = twoSum(nums,i+1,size-1,-nums[i],nums[i]); //result即为子函数的answer
            answer.insert(answer.end(),result.begin(),result.end()); //在anwer.end处插入数组
        }
        return answer;
    }
};
解释:
  1. 将数组元素排序,依次遍历一遍,将三数之和问题转变为两数之和问题;

    排序的目的是为了方便执行双指针算法,从两侧依次逼近target;

  2. twoSum函数

    1. 定义结果子数组:vector<vector> answer,数组中的元素还为数组时,这样定义;

    2. 第一个指针start指向输入元素的后一个元素,第二个指针end,即剩余元素的最小值和最大值,sum等于二者之和;

    3. 当sum大于target值时,end指针前移;当sum小于target值时,start指针后移;

    4. 当sum等于target时,先将当前元素包装起来(即先赋值到一个临时数组中),再将包装后的元素赋值到answer数组中;

      同时start指针后移,end指针前移,继续搜索有无其他成立的数组元素。

  3. threeSum函数

    1. 定义结果数组,通上面定义结果子数组一样;
    2. 利用sort函数将数组元素排序,方便执行双指针操作;
    3. 开始遍历数组元素,如果遇到元素相同的,则直接跳过(利用if判断和continue语句实现),遍历过程中执行twoSum子函数,并将输出结果插入到answer.end(),即尾后迭代器的位置。
    4. 返回整个结果数组answer。

2. 350-两个数组的交集II

题目:

给定两个数组,编写一个函数来计算它们的交集。

(输出元素出现的次数等于两个数组元素出现次数的最小值

示例1:
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2,2]
示例2:
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[4,9]

(将每个元素视为独立个体,即检测独立个体的出现次数)

方法一:哈希表

程序:
class Solution {
public:
    vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
        if (nums1.size() > nums2.size()) { //始终确保nums1为较短的数组
            return intersect(nums2, nums1);
        }
        unordered_map <int, int> m;
        for (int num : nums1) {
            ++m[num]; //构建哈希表
        }
        vector<int> intersection;
        for (int num : nums2) { //遍历nums2数组
            if (m.count(num)) { //返回哈希表m中含有“num”键的数量
                intersection.push_back(num); //向结果数组中压入num
                --m[num]; //num数量减一
                if (m[num] == 0) { //num数量为0时,哈希表m删除该元素
                    m.erase(num);
                }
            }
        }
        return intersection; //返回结果数组
    }
};
解释:
  1. 构建哈希表,将nums1元素存入哈希表,值为nums1元素数量,键为nums1元素值;(归根到底还是一种排序的方式)
  2. 遍历数组nums2
    1. 如果哈希表中存在nums2元素,则将该元素压入结果数组;
    2. 并将哈希表该元素的值-1;
    3. 若该元素值为0,则删除哈希表该元素。
  3. 返回结果数组。

方法二:排序+双指针

程序:
class Solution {
public:
    vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
        sort(nums1.begin(), nums1.end());
        sort(nums2.begin(), nums2.end()); //将数组1和数组2进行排序
        int length1 = nums1.size(), length2 = nums2.size();
        vector<int> intersection;
        int index1 = 0, index2 = 0;
        while (index1 < length1 && index2 < length2) { //当指针均未超出数组长度
            if (nums1[index1] < nums2[index2]) {
                index1++; //数组1元素小于数组2,则数组1指针后移
            } else if (nums1[index1] > nums2[index2]) {
                index2++; //数组1元素大于数组2,则数组2指针后移
            } else { //数组1和数组2元素相等时
                intersection.push_back(nums1[index1]); //数据压入结果数组
                index1++;
                index2++; //两个指针同时后移
            }
        }
        return intersection;
    }
};
解释:
  1. 先将两个数组进行排序(sort函数);
  2. 当两个指针均未超出数组长度时:
    1. 若数组1元素小于数组2,则数组1指针后移;
    2. 若数组1元素大于数组2,则数组2指针后移;
    3. 若二者元素相等,则将此时元素数值压入结果数组(push_back函数),并将两个指针同时后移
  3. 返回结果数组

3. 1300-转变数组后最进阶目标值的数组和

题目:

给定一个整数数组arr和一个目标值target,请返回一个整数value,使得将数组中所有大于value的变成value后,数组的和最接近target(最接近表示两者之差的绝对值最小)

如果有多种使得和最接近target的方案,请返回这些整数中的最小值。

答案不一定是arr中的数字。

示例1:
输入:arr = [4,9,3], target = 10
输出:3
解释:当选择value为3时,数组变为[3,3,3],和为9,差值为1;当选择value为4时,数组变为[3,4,4],和为11,差值为1。但3比4小,所以输出3。

方法:二分查找

程序:
class Solution {
public:

	int Sum_Vector(vector<int> arr, int res) {  //计算数组和
		int ret = 0;
		for (auto num : arr)
			ret += num >= res ? res : num;
		return ret;
	}

    int findBestValue(vector<int>& arr, int target) {

	sort(arr.begin(),arr.end());
	int n = arr.size();
	vector<int> presum(n + 1);
	for (int i = 1; i <= n; i++)	//获取前缀和
		presum[i] = presum[i-1] + arr[i - 1];

	int value;
	int min = 0, max = *max_element(arr.begin(), arr.end()); //二分查找的范围,即val的范围
	while (min <= max) {
		int mid = (min + max) / 2;
		auto iter = lower_bound(arr.begin(), arr.end(), mid);	//返回不小于mid的第一个值的正向迭代器
		int cur = presum[iter - arr.begin()] + (arr.end() - iter)*mid; //计算当前数组和
		if (cur <= target) {
			value = mid;
			min = mid + 1;
		}
		else
			max = mid - 1;
	}
	return (abs(Sum_Vector(arr, value) - target) <= abs(Sum_Vector(arr, value + 1) - target) ? value : value + 1);
    }
};
解释:
  1. 对于给定数组,首先先将其进行排序;
  2. 接着通过遍历数组中元素,获取整个数组对应的前缀和数组
  3. 根据题意,确定value的取值范围为[0, max(arr)],程序里设置为[min,max]
    • 设置while循环,通过二分法查找到满足条件“数组和小于target”的最大数组和对应的value;
    • 二分法的过程是,若数组和小于target,则提高下限min;否则降低上限max。
  4. 因为增大value值,对应的数组和是严格单调递增的,所以最终的value一定是上述步骤求出的value和value+1之中的一个,所以只需要对比二者的数组和大小即可得到结果。

Leetcode题目解析

原文:https://www.cnblogs.com/eesaltedfish/p/14710879.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!