696. 计数二进制子串;134. 加油站(贪心算法);147. 对链表进行插入排序;143. 重排链表;150. 逆波兰表达式求值

本文探讨了多种算法挑战,包括计算具有相等数量0和1的子字符串数量、寻找能完成环路行驶的加油站编号、链表的插入排序及重排、以及逆波兰表示法的计算。通过具体示例解析了每种算法的实现思路。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

给定一个字符串 s,计算具有相同数量0和1的非空(连续)子字符串的数量,并且这些子字符串中的所有0和所有1都是组合在一起的。

重复出现的子串要计算它们出现的次数。

示例 1 :

输入: "00110011"
输出: 6
解释: 有6个子串具有相同数量的连续1和0:“0011”,“01”,“1100”,“10”,“0011” 和 “01”。

请注意,一些重复出现的子串要计算它们出现的次数。

另外,“00110011”不是有效的子串,因为所有的0(和1)没有组合在一起。


示例 2 :

输入: "10101"
输出: 4
解释: 有4个子串:“10”,“01”,“10”,“01”,它们具有相同数量的连续1和0。


注意:


    s.length 在1到50,000之间。
    s 只包含“0”或“1”字符。

class Solution {
public:
    int countBinarySubstrings(string s) {
        if (s.size() == 0) return 0;
        vector<int> vec_cnt;
        int cnt = 1;
        for(int i = 1; i < s.size(); ++i) {
            if (s[i] == s[i-1]) {
                ++cnt;
            } else {
                vec_cnt.push_back(cnt);
                cnt = 1;
            }
        }
        vec_cnt.push_back(cnt);
        cnt = 0;
        for(int i = 1; i < vec_cnt.size(); ++i) {
            cnt += min(vec_cnt[i], vec_cnt[i-1]);
        }
        return cnt;
    }
};

class Solution {
public:
    int countBinarySubstrings(string s) {
        if (s.size() == 0) return 0;
        int cnt_pre = 0, cnt_cur = 1, cnt_res = 0;
        for (int i = 1; i < s.size(); ++i) {
            if (s[i] == s[i-1]) {
                ++cnt_cur;
            } else {
                cnt_res += min(cnt_pre, cnt_cur);
                cnt_pre = cnt_cur;
                cnt_cur = 1;
            }
        }
        cnt_res += min(cnt_pre, cnt_cur);
        return cnt_res;      
    }
};

在一条环路上有 N 个加油站,其中第 i 个加油站有汽油 gas[i] 升。

你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。

如果你可以绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1。

说明: 


    如果题目有解,该答案即为唯一答案。
    输入数组均为非空数组,且长度相同。
    输入数组中的元素均为非负数。


示例 1:

输入: 
gas  = [1,2,3,4,5]
cost = [3,4,5,1,2]

输出: 3

解释:
从 3 号加油站(索引为 3 处)出发,可获得 4 升汽油。此时油箱有 = 0 + 4 = 4 升汽油
开往 4 号加油站,此时油箱有 4 - 1 + 5 = 8 升汽油
开往 0 号加油站,此时油箱有 8 - 2 + 1 = 7 升汽油
开往 1 号加油站,此时油箱有 7 - 3 + 2 = 6 升汽油
开往 2 号加油站,此时油箱有 6 - 4 + 3 = 5 升汽油
开往 3 号加油站,你需要消耗 5 升汽油,正好足够你返回到 3 号加油站。
因此,3 可为起始索引。

示例 2:

输入: 
gas  = [2,3,4]
cost = [3,4,3]

输出: -1

解释:
你不能从 0 号或 1 号加油站出发,因为没有足够的汽油可以让你行驶到下一个加油站。
我们从 2 号加油站出发,可以获得 4 升汽油。 此时油箱有 = 0 + 4 = 4 升汽油
开往 0 号加油站,此时油箱有 4 - 3 + 2 = 3 升汽油
开往 1 号加油站,此时油箱有 3 - 3 + 3 = 3 升汽油
你无法返回 2 号加油站,因为返程需要消耗 4 升汽油,但是你的油箱只有 3 升汽油。
因此,无论怎样,你都不可能绕环路行驶一周。

//优先队列好像毫无意义,耗时耗空间,还不如直接遍历检查每个符合gas[i] - cost[i] >= 0的点开始循环检查
class Solution {
public:
    int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
        if (gas.size() == 0) return -1;
        auto cmp = [&gas, &cost](int &i, int &j) {                      //
            return gas[i] - cost[i] < gas[j] - cost[j];
        };
        priority_queue<int, vector<int>, decltype(cmp)> que_pos(cmp);   // pos with max(gas[i] - cost[i])
        for (int i = 0; i < gas.size(); ++i) {
            que_pos.push(i);
        }   
        while (que_pos.size()) {
            auto cur_pos = que_pos.top();
            if (gas[cur_pos] - cost[cur_pos] < 0) return -1;
            que_pos.pop();
            int sum_diff = 0;
            for (int i = 0; i < gas.size(); ++i) {
                auto tmp_pos = (cur_pos + i) % gas.size();
                sum_diff += gas[tmp_pos] - cost[tmp_pos];
                if (sum_diff < 0) break;
            }
            if (sum_diff >= 0) return cur_pos;
        }
        return -1;
    }
};
class Solution {    // 如果A站不能到B站,那么A,B之间到任何一个站都不能到B站,(B站是A站第一个不能到的站)
public:
    int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
        if (gas.size() == 0) return -1;
        int pos_A = 0, sum_diff = 0, all_sum_diff = 0;
        for (int i = 0; i < gas.size(); ++i) {
            all_sum_diff += gas[i] - cost[i];
            sum_diff += gas[i] - cost[i];
            if (sum_diff < 0) {
                pos_A = i + 1;
                sum_diff = 0;
                continue;
            }
        }
        return all_sum_diff >= 0 ? pos_A : -1;
    }
};

对链表进行插入排序。


插入排序的动画演示如上。从第一个元素开始,该链表可以被认为已经部分排序(用黑色表示)。
每次迭代时,从输入数据中移除一个元素(用红色表示),并原地将其插入到已排好序的链表中。

 

插入排序算法:


    插入排序是迭代的,每次只移动一个元素,直到所有元素可以形成一个有序的输出列表。
    每次迭代中,插入排序只从输入数据中移除一个待排序的元素,找到它在序列中适当的位置,并将其插入。
    重复直到所有输入数据插入完为止。


 

示例 1:

输入: 4->2->1->3
输出: 1->2->3->4


示例 2:

输入: -1->5->3->4->0
输出: -1->0->3->4->5

class Solution {
public:
    ListNode* insertionSortList(ListNode* head) {
        ListNode *LN = new ListNode(0);
        auto cur = head;
        while (cur) {
            auto tmp = LN->next, pre = LN;
            while (tmp && tmp->val <= cur->val) {
                pre = tmp;
                tmp = tmp->next;
            }
            pre->next = cur;
            cur = cur->next;        // 顺序问题
            pre->next->next = tmp;  //        
        }
        cur = LN->next;
        delete LN;
        return cur;
    }
};

给定一个单链表 L:L0→L1→…→Ln-1→Ln ,
将其重新排列后变为: L0→Ln→L1→Ln-1→L2→Ln-2→…

你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

示例 1:

给定链表 1->2->3->4, 重新排列为 1->4->2->3.

示例 2:

给定链表 1->2->3->4->5, 重新排列为 1->5->2->4->3.

class Solution {
public:
    void reorderList(ListNode* head) {
        if (!head || !head->next) return ;
        auto slow = head, fast = head->next;
        while (fast && fast->next){
            slow = slow->next;
            fast = fast->next->next;
        }
        auto cur = slow->next;
        slow->next = nullptr;
        ListNode *pre = nullptr, *next;
        while (cur) {
            next = cur->next;
            cur->next = pre;
            pre = cur;
            cur = next;
        }
        auto first = head, second = pre;
        while (second) {
            next = first->next;
            first->next = second;
            second = second->next;
            first->next->next = next;
            first = next;
        }
    }
};

根据 逆波兰表示法,求表达式的值。

有效的运算符包括 +, -, *, / 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。

 

说明:


    整数除法只保留整数部分。
    给定逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。


 

示例 1:

输入: ["2", "1", "+", "3", "*"]
输出: 9
解释: 该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9


示例 2:

输入: ["4", "13", "5", "/", "+"]
输出: 6
解释: 该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6


示例 3:

输入: ["10", "6", "9", "3", "+", "-11", "*", "/", "*", "17", "+", "5", "+"]
输出: 22
解释: 
该算式转化为常见的中缀算术表达式为:
  ((10 * (6 / ((9 + 3) * -11))) + 17) + 5
= ((10 * (6 / (12 * -11))) + 17) + 5
= ((10 * (6 / -132)) + 17) + 5
= ((10 * 0) + 17) + 5
= (0 + 17) + 5
= 17 + 5
= 22

 

逆波兰表达式:

逆波兰表达式是一种后缀表达式,所谓后缀就是指算符写在后面。


    平常使用的算式则是一种中缀表达式,如 ( 1 + 2 ) * ( 3 + 4 ) 。
    该算式的逆波兰表达式写法为 ( ( 1 2 + ) ( 3 4 + ) * ) 。


逆波兰表达式主要有以下两个优点:


    去掉括号后表达式无歧义,上式即便写成 1 2 + 3 4 + * 也可以依据次序计算出正确结果。
    适合用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中。

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int> sta;
        for (auto &t: tokens) {
            if (t == "+" || t == "-" || t == "*" || t == "/") {     // t == '+' 报错string和char不能比较
                int le, ri;
                ri = sta.top();                                     // le 和 ri顺序易错点
                sta.pop();
                le = sta.top();                                     // le 和 ri顺序易错点
                sta.pop();
                int pop_val;
                if (t == "+")                                       // switch... case "+" 依旧报错
                    pop_val = le + ri;
                else if (t == "-")
                    pop_val = le - ri;
                else if (t == "*")
                    pop_val = le * ri;
                else
                    pop_val = le / ri; 
                sta.push(pop_val);
            } else {
                sta.push(stoi(t));
            }
        }
        return sta.top();
    }
};

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值