LeetCode 679 - 24 Game


https://leetcode.com/articles/24-game/
You have 4 cards each containing a number from 1 to 9. You need to judge whether they could operated through */+-() to get the value of 24.
Example 1:
Input: [4, 1, 8, 7]
Output: True
Explanation: (8-4) * (7-1) = 24
Example 2:
Input: [1, 2, 1, 2]
Output: False
Note:
  1. The division operator / represents real division, not integer division. For example, 4 / (1 - 2/3) = 12.
  2. Every operation done is between two numbers. In particular, we cannot use - as a unary operator. For example, with [1, 1, 1, 1] as input, the expression -1 - 1 - 1 - 1 is not allowed.
  3. You cannot concatenate numbers together. For example, if the input is [1, 2, 1, 2], we cannot write this as 12 + 12

http://www.cnblogs.com/grandyang/p/8395062.html
这道题就是经典的24点游戏了,记得小时候经常玩这个游戏,就是每个人发四张牌,看谁最快能算出24,这完全是脑力大比拼啊,不是拼的牌技。玩的多了,就会摸出一些套路来,比如尽量去凑2和12,3和8,4和6等等,但是对于一些特殊的case,比如 [1, 5, 5, 5] 这种,正确的解法是 5 * (5 - 1 / 5),一般人都会去试加减乘,和能整除的除法,而像这种带小数的确实很难想到,但是程序计算就没问题,可以遍历所有的情况,这也是这道题的实际意义所在吧。

那么既然是要遍历所有的情况,我们应该隐约感觉到应该是要使用递归来做的。我们想,任意的两个数字之间都可能进行加减乘除,其中加法和乘法对于两个数字的前后顺序没有影响,但是减法和除法是有影响的,而且做除法的时候还要另外保证除数不能为零。我们要遍历任意两个数字,然后对于这两个数字,尝试各种加减乘除后得到一个新数字,将这个新数字加到原数组中,记得原来的两个数要移除掉,然后调用递归函数进行计算,我们可以发现每次调用递归函数后,数组都减少一个数字,那么当减少到只剩一个数字了,就是最后的计算结果,所以我们在递归函数开始时判断,如果数组只有一个数字,且为24,说明可以算出24,结果res赋值为true返回。这里我们的结果res是一个全局的变量,如果已经为true了,就没必要再进行运算了,所以第一行应该是判断结果res,为true就直接返回了。我们遍历任意两个数字,分别用p和q来取出,然后进行两者的各种加减乘除的运算,将结果保存进数组临时数组t,记得要判断除数不为零。然后将原数组nums中的p和q移除,遍历临时数组t中的数字,将其加入数组nums,然后调用递归函数,记得完成后要移除数字,恢复状态,这是递归解法很重要的一点。最后还要把p和q再加回原数组nums,这也是还原状态
    bool judgePoint24(vector<int>& nums) {
        bool res = false;
        double eps = 0.001;
        vector<double> arr(nums.begin(), nums.end());
        helper(arr, eps, res);
        return res;
    }
    void helper(vector<double>& nums, double eps, bool& res) {
        if (res) return;
        if (nums.size() == 1) {
            if (abs(nums[0] - 24) < eps) res = true;
            return;
        }
        for (int i = 0; i < nums.size(); ++i) {
            for (int j = 0; j < i; ++j) {
                double p = nums[i], q = nums[j];
                vector<double> t{p + q, p - q, q - p, p * q};
                if (p > eps) t.push_back(q / p);
                if (q > eps) t.push_back(p / q);
                nums.erase(nums.begin() + i);
                nums.erase(nums.begin() + j);
                for (double d : t) {
                    nums.push_back(d);
                    helper(nums, eps, res);
                    nums.pop_back();
                }
                nums.insert(nums.begin() + j, q);
                nums.insert(nums.begin() + i, p);
            }
        }
    }
https://leetcode.com/problems/24-game/discuss/107673/JAVA-Easy-to-understand.-Backtracking.
    boolean res = false;
    final double eps = 0.001;

    public boolean judgePoint24(int[] nums) {
        List<Double> arr = new ArrayList<>();
        for(int n: nums) arr.add((double) n);
        helper(arr);
        return res;
    }

    private void helper(List<Double> arr){
        if(res) return;
        if(arr.size() == 1){
            if(Math.abs(arr.get(0) - 24.0) < eps)
                res = true;
            return;
        }
        for (int i = 0; i < arr.size(); i++) {
            for (int j = 0; j < i; j++) {
                List<Double> next = new ArrayList<>();
                Double p1 = arr.get(i), p2 = arr.get(j);
                next.addAll(Arrays.asList(p1+p2, p1-p2, p2-p1, p1*p2));
                if(Math.abs(p2) > eps)  next.add(p1/p2);
                if(Math.abs(p1) > eps)  next.add(p2/p1);
                
                arr.remove(i);
                arr.remove(j);
                for (Double n: next){
                    arr.add(n);
                    helper(arr);
                    arr.remove(arr.size()-1);
                }
                arr.add(j, p2);
                arr.add(i, p1);
            }
        }
    }


来看一种很不同的递归写法,这里将加减乘除操作符放到了一个数组ops中。并且没有用全局变量res,而是让递归函数带有bool型返回值。在递归函数中,还是要先看nums数组的长度,如果为1了,说明已经计算完成,直接看结果是否为0就行了。然后遍历任意两个数字,注意这里的i和j都分别从0到了数组长度,而上面解法的j是从0到i,这是因为上面解法将p - q, q - p, q / q, q / p都分别列出来了,而这里仅仅是nums[i] - nums[j], nums[i] / nums[j],所以i和j要交换位置,但是为了避免加法和乘法的重复计算,我们可以做个判断,还有别忘记了除数不为零的判断,i和j不能相同的判断。我们建立一个临时数组t,将非i和j位置的数字都加入t,然后遍历操作符数组ops,每次取出一个操作符,然后将nums[i]和nums[j]的计算结果加入t,调用递归函数,如果递归函数返回true了,那么就直接返回true。否则移除刚加入的结果,还原t的状态
    bool judgePoint24(vector<int>& nums) {
        double eps = 0.001;
        vector<char> ops{'+', '-', '*', '/'};
        vector<double> arr(nums.begin(), nums.end());
        return helper(arr, ops, eps);
    }
    bool helper(vector<double>& nums, vector<char>& ops, double eps) {
        if (nums.size() == 1) return abs(nums[0] - 24) < eps;
        for (int i = 0; i < nums.size(); ++i) {
            for (int j = 0; j < nums.size(); ++j) {
                if (i == j) continue;
                vector<double> t;
                for (int k = 0; k < nums.size(); ++k) {
                    if (k != i && k != j) t.push_back(nums[k]);
                }
                for (char op : ops) {
                    if ((op == '+' || op == '*') && i > j) continue;
                    if (op == '/' && nums[j] < eps) continue;
                    switch(op) {
                        case '+': t.push_back(nums[i] + nums[j]); break;
                        case '-': t.push_back(nums[i] - nums[j]); break;
                        case '*': t.push_back(nums[i] * nums[j]); break;
                        case '/': t.push_back(nums[i] / nums[j]); break;
                    }
                    if (helper(t, ops, eps)) return true;
                    t.pop_back();
                }
            }
        }
        return false;
    }

X.
http://shibaili.blogspot.com/2018/11/679-24-game.html
排列组合。任取2个数,对它们进行四则运算,然后放回数组。直到数组大小为1
注意:这种方法可以处理掉括号
follow up
#1 打印出所有答案:开一个额外的数组作为参数记录(见下)
#2 如果括号不让用:应该是更简单。把所有排列组合写出来,然后写一个evaluate方法来计算。
    public boolean judgePoint24(int[] nums) {
        List<Double> l = new ArrayList<>();
        for (int i : nums) {
            l.add((double) i);
        }
        return helper(l);
    }
     
    private boolean helper(List<Double> nums) {
        if (nums.size() == 1) {           
            if (Math.abs(nums.get(0) - 24.0) < 1e-6) {
                return true;  
            }
            return false;
        }
         
        for (int i = 0; i < nums.size(); i++) {
            for (int j = 0; j < nums.size(); j++) {
                if (i == j || nums.get(i) == nums.get(j)) continue;
                 
                List<Double> tmp = new ArrayList<>();
                for (int k = 0; k < nums.size(); k++) {
                    if (k != i && k != j) tmp.add(nums.get(k));
                }
                 
                double n1 = nums.get(i);
                double n2 = nums.get(j);
                for (int k = 0; k < 4; k++) {
                    double val = 0;
                    if (k < 2 && j < i) continue;
                    if (k == 0) val = n1 + n2;
                    if (k == 1) val = n1 * n2;
                    if (k == 2) val = n1 - n2;
                    if (k == 3) {
                        if (n2 != 0) val = n1 / n2;
                        else continue;
                    }
                     
                    tmp.add(val);
                    if (helper(tmp)) return true;
                    tmp.remove(tmp.size() - 1);
                }
            }
        }
         
        return false;
    }


https://leetcode.com/problems/24-game/discuss/113972/Very-Easy-JAVA-DFS
There are only 4 cards and only 4 operations that can be performed. Even when all operations do not commute, that gives us an upper bound of 12 * 6 * 2 * 4 * 4 * 4 = 9216 possibilities, which makes it feasible to just try them all. Specifically, we choose two numbers (with order) in 12 ways and perform one of 4 operations (12 * 4). Then, with 3 remaining numbers, we choose 2 of them and perform one of 4 operations (6 * 4). Finally we have two numbers left and make a final choice of 2 * 4 possibilities.
We will perform 3 binary operations (+, -, *, / are the operations) on either our numbers or resulting numbers. Because - and / do not commute, we must be careful to consider both a / b and b / a.
For every way to remove two numbers a, b in our list, and for each possible result they can make, like a+ba/b, etc., we will recursively solve the problem on this smaller list of numbers.
  • Time Complexity: O(1). There is a hard limit of 9216 possibilities, and we do O(1) work for each of them.
  • Space Complexity: O(1). Our intermediate arrays are at most 4 elements, and the number made is bounded by an O(1) factor.

  public boolean judgePoint24(int[] nums) {
    ArrayList A = new ArrayList<Double>();
    for (int v : nums)
      A.add((double) v);
    return solve(A);
  }

  private boolean solve(ArrayList<Double> nums) {
    if (nums.size() == 0)
      return false;
    if (nums.size() == 1)
      return Math.abs(nums.get(0) - 24) < 1e-6;

    for (int i = 0; i < nums.size(); i++) {
      for (int j = 0; j < nums.size(); j++) {
        if (i != j) {
          ArrayList<Double> nums2 = new ArrayList<Double>();
          for (int k = 0; k < nums.size(); k++)
            if (k != i && k != j) {
              nums2.add(nums.get(k));
            }
          for (int k = 0; k < 4; k++) {
            if (k < 2 && j > i)
              continue;
            if (k == 0)
              nums2.add(nums.get(i) + nums.get(j));
            if (k == 1)
              nums2.add(nums.get(i) * nums.get(j));
            if (k == 2)
              nums2.add(nums.get(i) - nums.get(j));
            if (k == 3) {
              if (nums.get(j) != 0) {
                nums2.add(nums.get(i) / nums.get(j));
              } else {
                continue;
              }
            }
            if (solve(nums2))
              return true;
            nums2.remove(nums2.size() - 1);
          }
        }
      }
    }
    return false;

  }

X. https://blog.csdn.net/magicbean2/article/details/79233951

    bool judgePoint24(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        do {
            if (valid(nums)) {
                return true;
            }
        } while (next_permutation(nums.begin(), nums.end()));
        return false;
    }
private:
    bool valid(vector<int>& nums) {
        double a = nums[0], b = nums[1], c = nums[2], d = nums[3];
        if (valid(a + b, c, d) || valid(a - b, c, d) || valid(a * b, c, d) || valid(a / b, c, d)) {
            return true;
        }
        if (valid(a, b + c, d) || valid(a, b - c, d) || valid(a, b * c, d) || valid(a, b / c, d)) {
            return true;
        }
        if (valid(a, b, c + d) || valid(a, b, c - d) || valid(a, b, c * d) || valid(a, b, c / d)) {
            return true;
        }
        return false;
    }
    bool valid(double a, double b, double c) {
        if (valid(a + b, c) || valid(a - b, c) || valid(a * b, c) || b&&valid(a / b, c)) {
            return true;
        }
        if (valid(a, b + c) || valid(a, b - c) || valid(a, b * c) || c&&valid(a, b / c)) {
            return true;
        }
        return false;
    }
    bool valid(double a, double b) {
        if (abs(a + b - 24.0) < 0.0001 || abs(a - b - 24.0) < 0.0001 ||
            abs(a * b - 24.0) < 0.0001 || b && abs(a / b - 24.0) < 0.0001) {
            return true;
        }
        return false;
    }
}

Follow #1 打印出所有答案
额外的数组里存的是为被计算前的值,如 “(1+3)”。对应原来的数组存的是4.
public static boolean judgePoint24(int[] nums) {
       List<Double> l = new ArrayList<>();
       List<String> s = new ArrayList<>();
       for (int i : nums) {
           l.add((double) i);
           s.add(Integer.toString(i));
       }
       List<String> rt = new ArrayList<>();
       helper(l, s, rt);
       for (String ss : rt) {
           System.out.println(ss);
       }
       return true;
   }
   private static void helper(List<Double> nums, List<String> s, List<String> rt) {
       if (nums.size() == 1) {
           if (Math.abs(nums.get(0) - 24.0) < 1e-6) {
               rt.add(s.get(0));
           }
           return;
       }
       for (int i = 0; i < nums.size(); i++) {
           for (int j = 0; j < nums.size(); j++) {
               if (i == j || nums.get(i) == nums.get(j)) continue;
               List<Double> tmp = new ArrayList<>();
               List<String> tmpS = new ArrayList<>();
               for (int k = 0; k < nums.size(); k++) {
                   if (k != i && k != j) {
                       tmp.add(nums.get(k));
                       tmpS.add(s.get(k));
                   }
               }
               double n1 = nums.get(i);
               double n2 = nums.get(j);
               for (int k = 0; k < 4; k++) {
                   double val = 0;
                   String ss = "";
                   if (k < 2 && j < i) continue;
                   if (k == 0) {
                       val = n1 + n2;
                       ss = "(" + s.get(i) + "+" + s.get(j) + ")";
                   }
                   if (k == 1) {
                       val = n1 * n2;
                       ss = "(" + s.get(i) + "*" + s.get(j) + ")";
                   }
                   if (k == 2) {
                       val = n1 - n2;
                       ss = "(" + s.get(i) + "-" + s.get(j) + ")";
                   }
                   if (k == 3) {
                       if (n2 != 0) {
                           val = n1 / n2;
                           ss = "(" + s.get(i) + "/" + s.get(j) + ")";
                       }
                       else continue;
                   }
                   tmp.add(val);
                   tmpS.add(ss);
                   helper(tmp, tmpS, rt);
                   tmp.remove(tmp.size() - 1);
                   tmpS.remove(tmpS.size() - 1);
               }
           }
       }
       return;
   }

讨论:博主在调试的时候,遇到了这个test case: [1, 3, 4, 6],返回的是true,但是博主心算了一会,并没有想出其是如何算出24的。所以博主在想,能不能修改下代码,使得其能将运算的过程返回出来。其实并不难改,基于解法二来改一下,我们发现,计算后的结果被存入了临时数组t,进行下一次递归,我们需要将这个过程保存下来,用一个字符串数组,比如"1+3",或者"1-3"等等,这个数组跟数组t大小相同,操作基本相同,同时需要被传入到下一次递归函数中,而在下一次递归函数中,数组t中取出的就是4和-2,但是字符串数组就可以取出"1+3"和"1-3",我们就可以继续和别的数进行计算了,比如要乘以4,我们需要给取出的字符串加上括号,就变成了(1+3)*4了,就通过这种方法就可以将过程返回了,运行test case: [1, 3, 4, 6],返回得到:
(6/(1-(3/4)))
没有问题,还有就是,由于组成24的方法可能不止1种,我们可以将所有情况都返回,那么我们的递归函数就不要有返回值,这样可以遍历完所有的情况,对于test case: [1, 2, 3, 8],返回如下:
((8-2)*(1+3))
(8/(1-(2/3)))
(3/((2-1)/8))
(3*(8/(2-1)))
(8*(3*(2-1)))
(2*(1+(3+8)))
(8/((2-1)/3))
(3*(8*(2-1)))
((1+3)*(8-2))
((3*8)/(2-1))
(8*(3/(2-1)))
((3*8)*(2-1))
(2*(8+(1+3)))
((2-1)*(3*8))
(2*(3+(1+8)))
被惊到了有木有!居然有这么多种计算方法可以得到24~ (修改后的代码贴在了评论区二楼 :)


Labels

LeetCode (1432) GeeksforGeeks (1122) LeetCode - Review (1067) Review (882) Algorithm (668) to-do (609) Classic Algorithm (270) Google Interview (237) Classic Interview (222) Dynamic Programming (220) DP (186) Bit Algorithms (145) POJ (141) Math (137) Tree (132) LeetCode - Phone (129) EPI (122) Cracking Coding Interview (119) DFS (115) Difficult Algorithm (115) Lintcode (115) Different Solutions (110) Smart Algorithm (104) Binary Search (96) BFS (91) HackerRank (90) Binary Tree (86) Hard (79) Two Pointers (78) Stack (76) Company-Facebook (75) BST (72) Graph Algorithm (72) Time Complexity (69) Greedy Algorithm (68) Interval (63) Company - Google (62) Geometry Algorithm (61) Interview Corner (61) LeetCode - Extended (61) Union-Find (60) Trie (58) Advanced Data Structure (56) List (56) Priority Queue (53) Codility (52) ComProGuide (50) LeetCode Hard (50) Matrix (50) Bisection (48) Segment Tree (48) Sliding Window (48) USACO (46) Space Optimization (45) Company-Airbnb (41) Greedy (41) Mathematical Algorithm (41) Tree - Post-Order (41) ACM-ICPC (40) Algorithm Interview (40) Data Structure Design (40) Graph (40) Backtracking (39) Data Structure (39) Jobdu (39) Random (39) Codeforces (38) Knapsack (38) LeetCode - DP (38) Recursive Algorithm (38) String Algorithm (38) TopCoder (38) Sort (37) Introduction to Algorithms (36) Pre-Sort (36) Beauty of Programming (35) Must Known (34) Binary Search Tree (33) Follow Up (33) prismoskills (33) Palindrome (32) Permutation (31) Array (30) Google Code Jam (30) HDU (30) Array O(N) (29) Logic Thinking (29) Monotonic Stack (29) Puzzles (29) Code - Detail (27) Company-Zenefits (27) Microsoft 100 - July (27) Queue (27) Binary Indexed Trees (26) TreeMap (26) to-do-must (26) 1point3acres (25) GeeksQuiz (25) Merge Sort (25) Reverse Thinking (25) hihocoder (25) Company - LinkedIn (24) Hash (24) High Frequency (24) Summary (24) Divide and Conquer (23) Proof (23) Game Theory (22) Topological Sort (22) Lintcode - Review (21) Tree - Modification (21) Algorithm Game (20) CareerCup (20) Company - Twitter (20) DFS + Review (20) DP - Relation (20) Brain Teaser (19) DP - Tree (19) Left and Right Array (19) O(N) (19) Sweep Line (19) UVA (19) DP - Bit Masking (18) LeetCode - Thinking (18) KMP (17) LeetCode - TODO (17) Probabilities (17) Simulation (17) String Search (17) Codercareer (16) Company-Uber (16) Iterator (16) Number (16) O(1) Space (16) Shortest Path (16) itint5 (16) DFS+Cache (15) Dijkstra (15) Euclidean GCD (15) Heap (15) LeetCode - Hard (15) Majority (15) Number Theory (15) Rolling Hash (15) Tree Traversal (15) Brute Force (14) Bucket Sort (14) DP - Knapsack (14) DP - Probability (14) Difficult (14) Fast Power Algorithm (14) Pattern (14) Prefix Sum (14) TreeSet (14) Algorithm Videos (13) Amazon Interview (13) Basic Algorithm (13) Codechef (13) Combination (13) Computational Geometry (13) DP - Digit (13) LCA (13) LeetCode - DFS (13) Linked List (13) Long Increasing Sequence(LIS) (13) Math-Divisible (13) Reservoir Sampling (13) mitbbs (13) Algorithm - How To (12) Company - Microsoft (12) DP - Interval (12) DP - Multiple Relation (12) DP - Relation Optimization (12) LeetCode - Classic (12) Level Order Traversal (12) Prime (12) Pruning (12) Reconstruct Tree (12) Thinking (12) X Sum (12) AOJ (11) Bit Mask (11) Company-Snapchat (11) DP - Space Optimization (11) Dequeue (11) Graph DFS (11) MinMax (11) Miscs (11) Princeton (11) Quick Sort (11) Stack - Tree (11) 尺取法 (11) 挑战程序设计竞赛 (11) Coin Change (10) DFS+Backtracking (10) Facebook Hacker Cup (10) Fast Slow Pointers (10) HackerRank Easy (10) Interval Tree (10) Limited Range (10) Matrix - Traverse (10) Monotone Queue (10) SPOJ (10) Starting Point (10) States (10) Stock (10) Theory (10) Tutorialhorizon (10) Kadane - Extended (9) Mathblog (9) Max-Min Flow (9) Maze (9) Median (9) O(32N) (9) Quick Select (9) Stack Overflow (9) System Design (9) Tree - Conversion (9) Use XOR (9) Book Notes (8) Company-Amazon (8) DFS+BFS (8) DP - States (8) Expression (8) Longest Common Subsequence(LCS) (8) One Pass (8) Quadtrees (8) Traversal Once (8) Trie - Suffix (8) 穷竭搜索 (8) Algorithm Problem List (7) All Sub (7) Catalan Number (7) Cycle (7) DP - Cases (7) Facebook Interview (7) Fibonacci Numbers (7) Flood fill (7) Game Nim (7) Graph BFS (7) HackerRank Difficult (7) Hackerearth (7) Inversion (7) Kadane’s Algorithm (7) Manacher (7) Morris Traversal (7) Multiple Data Structures (7) Normalized Key (7) O(XN) (7) Radix Sort (7) Recursion (7) Sampling (7) Suffix Array (7) Tech-Queries (7) Tree - Serialization (7) Tree DP (7) Trie - Bit (7) 蓝桥杯 (7) Algorithm - Brain Teaser (6) BFS - Priority Queue (6) BFS - Unusual (6) Classic Data Structure Impl (6) DP - 2D (6) DP - Monotone Queue (6) DP - Unusual (6) DP-Space Optimization (6) Dutch Flag (6) How To (6) Interviewstreet (6) Knapsack - MultiplePack (6) Local MinMax (6) MST (6) Minimum Spanning Tree (6) Number - Reach (6) Parentheses (6) Pre-Sum (6) Probability (6) Programming Pearls (6) Rabin-Karp (6) Reverse (6) Scan from right (6) Schedule (6) Stream (6) Subset Sum (6) TSP (6) Xpost (6) n00tc0d3r (6) reddit (6) AI (5) Abbreviation (5) Anagram (5) Art Of Programming-July (5) Assumption (5) Bellman Ford (5) Big Data (5) Code - Solid (5) Code Kata (5) Codility-lessons (5) Coding (5) Company - WMware (5) Convex Hull (5) Crazyforcode (5) DFS - Multiple (5) DFS+DP (5) DP - Multi-Dimension (5) DP-Multiple Relation (5) Eulerian Cycle (5) Graph - Unusual (5) Graph Cycle (5) Hash Strategy (5) Immutability (5) Java (5) LogN (5) Manhattan Distance (5) Matrix Chain Multiplication (5) N Queens (5) Pre-Sort: Index (5) Quick Partition (5) Quora (5) Randomized Algorithms (5) Resources (5) Robot (5) SPFA(Shortest Path Faster Algorithm) (5) Shuffle (5) Sieve of Eratosthenes (5) Strongly Connected Components (5) Subarray Sum (5) Sudoku (5) Suffix Tree (5) Swap (5) Threaded (5) Tree - Creation (5) Warshall Floyd (5) Word Search (5) jiuzhang (5)

Popular Posts