日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

力扣- -241.为运算表达式设计优先级

發布時間:2024/4/11 编程问答 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 力扣- -241.为运算表达式设计优先级 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

力扣- -241.為運算表達式設計優先級(分治算法)

文章目錄

  • 力扣- -241.為運算表達式設計優先級(分治算法)
    • 一、題目描述
    • 二、分析
    • 三、代碼
    • 四、優化

一、題目描述

二、分析

  • 看到這道題的第一感覺肯定是復雜,我要窮舉出所有可能的加括號方式,是不是還要考慮括號的合法性?是不是還要考慮計算的優先級?

  • 是的,這些都要考慮,但是不需要我們來考慮。利用分治思想和遞歸函數,算法會幫我們考慮一切細節,也許這就是算法的魅力吧,哈哈哈。

廢話不多說,解決本題的關鍵有兩點:

  • 1、不要思考整體,而是把目光聚焦局部,只看一個運算符。

  • 該問題只要思考每個部分需要做什么,而不要思考整體需要做什么。

  • 說白了,解決遞歸相關的算法問題,就是一個化整為零的過程,你必須瞄準一個小的突破口,然后把問題拆解,大而化小,利用遞歸函數來解決。

  • 2、明確遞歸函數的定義是什么,相信并且利用好函數的定義。

  • 這也是前文經常提到的一個點,因為遞歸函數要自己調用自己,你必須搞清楚函數到底能干嘛,才能正確進行遞歸調用。

下面來具體解釋下這兩個關鍵點怎么理解。

  • 我們先舉個例子,比如我給你輸入這樣一個算式:
1 + 2 * 3 - 4 * 5
  • 請問,這個算式有幾種加括號的方式?請在一秒之內回答我。

  • 估計你回答不出來,因為括號可以嵌套,要窮舉出來肯定得費點功夫。

  • 不過呢,嵌套這個事情吧,我們人類來看是很頭疼的,但對于算法來說嵌套括號不要太簡單,一次遞歸就可以嵌套一層,一次搞不定大不了多遞歸幾次。

  • 所以,作為寫算法的人類,我們只需要思考,如果不讓括號嵌套(即只加一層括號),有幾種加括號的方式?

  • 還是上面的例子,顯然我們有四種加括號方式:

(1) + (2 * 3 - 4 * 5)(1 + 2) * (3 - 4 * 5)(1 + 2 * 3) - (4 * 5)(1 + 2 * 3 - 4) * (5)
  • 發現規律了么?其實就是按照運算符進行分割,給每個運算符的左右兩部分加括號,這就是之前說的第一個關鍵點,不要考慮整體,而是聚焦每個運算符。

  • 現在單獨說上面的第三種情況:

(1 + 2 * 3) - (4 * 5)
  • 我們用減號-作為分隔,把原算式分解成兩個算式1 + 2 * 3和4 * 5。

  • 分治分治,分而治之,這一步就是把原問題進行了「分」,我們現在要開始「治」了。

  • 1 + 2 * 3可以有兩種加括號的方式,分別是:

(1) + (2 * 3) = 7(1 + 2) * (3) = 9
  • 或者我們可以寫成這種形式:
1 + 2 * 3 = [9, 7]
  • 而4 * 5當然只有一種加括號方式,就是4 * 5 = [20]。

  • 然后呢,你能不能通過上述結果推導出(1 + 2 * 3) - (4 * 5)有幾種加括號方式,或者說有幾種不同的結果?

  • 顯然,可以推導出來(1 + 2 * 3) - (4 * 5)有兩種結果,分別是:

9 - 20 = -117 - 20 = -13
  • 那你可能要問了,1 + 2 * 3 = [9, 7]的結果是我們自己看出來的,如何讓算法計算出來這個結果呢?
vector<int> diffWaysToCompute(string input)
  • 這個函數不就是干這個事兒的嗎?這就是我們之前說的第二個關鍵點,明確函數的定義,相信并且利用這個函數定義。

  • 你甭管這個函數怎么做到的,你相信它能做到,然后用就行了,最后它就真的能做到了。

  • 那么,對于(1 + 2 * 3) - (4 * 5)這個例子,我們的計算邏輯其實就是這段代碼:

vector<int> diffWaysToCompute("(1 + 2 * 3) - (4 * 5)") {vector<int> res;/****** 分 ******/vector<int> left = diffWaysToCompute("1 + 2 * 3");vector<int> right = diffWaysToCompute("4 * 5");/****** 治 ******/for (int a : left)for (int b : right)res.emplace_back(a - b);return res; }
  • 好,現在(1 + 2 * 3) - (4 * 5)這個例子是如何計算的,你應該完全理解了吧,那么回來看我們的原始問題。

  • 原問題1 + 2 * 3 - 4 * 5是不是只有(1 + 2 * 3) - (4 * 5)這一種情況?是不是只能從減號-進行分割?

不是,每個運算符都可以把原問題分割成兩個子問題,剛才已經列出了所有可能的分割方式:

(1) + (2 * 3 - 4 * 5)(1 + 2) * (3 - 4 * 5)(1 + 2 * 3) - (4 * 5)(1 + 2 * 3 - 4) * (5)

所以,我們需要窮舉上述的每一種情況,可以進一步細化一下解法代碼

三、代碼

class Solution { public:vector<int> diffWaysToCompute(string input) {if(input.empty()) {return {};}//每個遞歸的結果vector<int> ret;//循環便利找符號for(size_t i = 0;i < input.size(); ++i) {if(input[i] == '+' || input[i] == '-' || input[i] == '*') {//遞歸左右vector<int> left = diffWaysToCompute(input.substr(0, i));vector<int> right = diffWaysToCompute(input.substr(i + 1, input.size() - i));//計算結果for(auto &l : left) {for( auto &r : right) {if(input[i] == '+') {ret.emplace_back(l + r);}else if(input[i] == '-') {ret.emplace_back(l - r);}else {ret.emplace_back(l * r);}}}}}//遞歸出口//代表本次遞歸下來沒找到符號,就是只有數字,直接返回即可if(ret.empty()) {ret.emplace_back(stoi(input));return ret;}return ret;} };
  • 這段代碼應該很好理解了吧,就是掃描輸入的算式input,每當遇到運算符就進行分割,遞歸計算出結果后,根據運算符來合并結果。

  • 這就是典型的分治思路,先「分」后「治」,先按照運算符將原問題拆解成多個子問題,然后通過子問題的結果來合成原問題的結果。

  • 當然,一個重點在這段代碼:

// base case // 如果 ret 為空,說明算式是一個數字,沒有運算符 if(ret.empty()) {ret.emplace_back(stoi(input));return ret; }
  • 遞歸函數必須有個 base case 用來結束遞歸,其實這段代碼就是我們分治算法的 base case,代表著你「分」到什么時候可以開始「治」。

  • 我們是按照運算符進行「分」的,一直這么分下去,什么時候是個頭?顯然,當算式中不存在運算符的時候就可以結束了。

  • 那為什么以ret.empty()作為判斷條件?因為當算式中不存在運算符的時候,就不會觸發 if 語句,也就不會給ret中添加任何元素。

四、優化

// 備忘錄 std::map<string, vector<int>> memo;vector<int> diffWaysToCompute(string input) {// 避免重復計算if (memo.count(input)) {return memo[input];}/****** 其他都不變 ******//***********************/// 將結果添加進備忘錄memo[input] = res;return res; }

總結

以上是生活随笔為你收集整理的力扣- -241.为运算表达式设计优先级的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。