Categories
木有技术

LeetCode 403. Frog Jump

A frog is crossing a river. The river is divided into x units and at each unit there may or may not exist a stone. The frog can jump on a stone, but it must not jump into the water.
Given a list of stones’ positions (in units) in sorted ascending order, determine if the frog is able to cross the river by landing on the last stone. Initially, the frog is on the first stone and assume the first jump must be 1 unit.
If the frog’s last jump was k units, then its next jump must be either k – 1, k, or k + 1 units. Note that the frog can only jump in the forward direction.
思路:这个题目可以看作是动态规划。青蛙的一个状态可以表示为{当前的k,当前的位置(可以用stone的索引表示)},最初的时候状态就是{ 1, 0 }。在某一个状态下,青蛙可以尝试向前移动{k-1, k, k+1}步,如果能成功跳到那就进入了下一个状态。要注意的是这种状态的遍历是有重复的,所以需要一个东西记录已经尝试过(当然是尝试失败的,不然直接return true)的状态们,不然很容易超时。
为了避免栈溢出,下文的解法直接用stack模拟函数的栈调用。

class Solution {
public:
    bool canCross(vector<int>& stones) {
        if (stones.empty())
            return false;
        else if (stones.size() == 1)
            return true;
        // state { k, index, has_traversed }
        stack<tuple<int, int, bool>> states;
        unordered_map<int, set<int>> visited;
        states.push({ 1, 0, false });
        while (!states.empty())
        {
            if (std::get<2>(states.top()) == true)
            {
                // pop state (like recursive function returning)
                states.pop();
                continue;
            }
            int last_k = std::get<0>(states.top());
            int last_pos_idx = std::get<1>(states.top());
            if (visited[last_k].count(last_pos_idx) > 0)
            {
                // avoid duplicated state entry
                states.pop();
                continue;
            }
            std::get<2>(states.top()) = true;
            visited[last_k].insert(last_pos_idx);
            if (last_pos_idx == stones.size() - 1)
                return true;
            for (int step = last_k - 1; step <= last_k + 1; ++step)
            {
                if (step == 0) continue;
                if (last_pos_idx == 0 && step != 1) continue; // can only move 1 step at the beginning
                int next_stone = stones[last_pos_idx] + step;
                for (int j = last_pos_idx + 1; j < stones.size(); ++j)
                {
                    if (stones[j] > next_stone) break;
                    if (stones[j] == next_stone)
                    {
                        states.push({ step, j , false}); //Push state of the next stone
                    }
                }
            }
        }
        return false;
    }
};

 

Categories
不学无术

LeetCode 368. Largest Divisible Subset

思路:
想到一个词叫“传递性”,在这边应该也适用。就是如果b%a==0,然后c%b==0,那么肯定有c%a==0. 按照这个方法来构造一个集合就可以了,应该是动态规划的思想,results[i]保存包含数组元素nums[i]的最佳集合,构建集合的时候,首先从之前的results[j] (j=0…i-1)里面找到能够“传递”过来的集合(nums[i]%nums[i]==0)选作最佳(相当于找到c%b==0,当然这里的b是之前results[j]里面的最大元素,即nums[j])然后加上本身的nums[i]就构成了到第i个元素为止的最佳集合,如此继续…
时间复杂度应该是O(n^2)

class Solution {
public:
    vector<int> largestDivisibleSubset(vector<int>& nums) {
        if(nums.empty())
            return nums;
        sort(nums.begin(), nums.end());
        int bestIndex = 0;
        vector<vector<int>> results(nums.size());
        for(int i=0; i<nums.size(); i++){
            for(int j=0; j<i; j++){
                //nums[j] < nums[i]
                if(nums[i]%nums[j]==0 &&
                    results[i].size() < results[j].size())
                    results[i] = results[j];
            }
            results[i].push_back(nums[i]);
            if(results[bestIndex].size() < results[i].size())
                bestIndex = i;
        }
        return results[bestIndex];
    }
};

 

Categories
不学无术

最长递增子串 (只能忽略m个元素)

记录一道碰到的笔试题。

题目

给定一个int数组,求其最长的连续递增子序列,但递增过程中可以忽略m个数组元素。在最优子序列长度相同时,以子序列元素之和较小的一个为最优解*。
即实现方法

vector<int> getLongestSubAry(const vector<int>& array, int m)

思路

八成是动态规划,参考最长连续子序列。
需要存储状态:第i个元素的{最优序列,忽略元素个数}
从i向前寻找m个位置的最优解(因为最多只能忽略m个)。假设那个“之前的”元素是array[j],那如果array[i]>array[j]并且array[j]+1>=当前最优序列长度,则判断限制条件*以确定是否更新最优解。
整个方法的复杂度应该是[latex]O(n*m)[/latex],其中n为array的长度。

代码

#include <iostream>
#include <vector>
using namespace std;
struct OptmElem{
    vector<int> results;
    int omitCount;
    int getResultCount(){
        return results.size();
    }
};
int getSum(const vector<int>& ary){
    int sum = 0;
    for(int i=0; i<ary.size(); i++)
        sum += ary[i];
    return sum;
}
/**
* Find the longest increasing subarray
* that allows skipping m elements
*/
vector<int> maxSubArray(const vector<int>& array, int m){
    if(array.empty())
        return vector<int>();
    vector<OptmElem> bestResults;
    OptmElem initOptm;
    initOptm.results.push_back(array[0]);
    initOptm.omitCount = m;
    bestResults.push_back(initOptm);
    unsigned long maxLen = 0;
    int optiIndex = 0; //index of optimal result in bestResults
    for(int i=1; i<array.size(); i++){
        vector<int> currBest = {array[i]};
        int omitCount = m;
        for(int j=(i-m>=0)?(i-m):(0); j<i; j++){
            //trace back j steps
            //if omittance limit exceed, continue
            if(bestResults[j].omitCount < (i-j-1)){
                continue;
            }
            if((array[i] > array[j]) && (bestResults[j].getResultCount()+1 >= currBest.size())){
                //new optimal
                if(bestResults[j].getResultCount()+1  == currBest.size()){
                    //on-par result, array that has the smallest sum is the best
                    int oldSum = getSum(currBest);
                    int newSum = getSum(bestResults[j].results) + array[i];
                    if(newSum > oldSum){
                        continue; //omit this best
                    }
                }
                currBest = bestResults[j].results;
                currBest.push_back(array[i]);
                omitCount = bestResults[j].omitCount - (i-j-1);
            }
        }
        OptmElem elem;
        elem.results = currBest;
        elem.omitCount = omitCount;
        bestResults.push_back(elem);
        if(maxLen < currBest.size()){
            maxLen = currBest.size();
            optiIndex = i;
        } else if (maxLen == currBest.size()){
            int oldBestSum = getSum(bestResults[optiIndex].results);
            int currBestSum = getSum(currBest);
            if(currBestSum < oldBestSum){
                optiIndex = i;
            }
        }
    }
    return bestResults[optiIndex].results;
}

 
 

Categories
不学无术

LeetCode 91. Decode Ways

思路

警告:千万注意’0’这个异类,当然了还有空输入:(。
其实也是个动态规划?的问题,有点像那个走楼梯的题
当前能decode的方法数与之前的两种状态有关,就是

  • 判断当前的字母s[i]能不能单个decode,如果可以,那方法数就要加上解码s[i-1]时的方法数,如果不能解那就不加;
  • 判断两个字符s[i-1]s[i]的组合能不能解码(就是在不在10-26的范围内),如果可以,拿加上解码s[i-2]时的方法数;

所以事实上只要保存s[i],s[i-1]两个方法数的结果就行,我的代码空间复杂度还比较大,但是不想改了。

代码

class Solution {
public:
    bool isLegalBiChar(char m, char n){
        // is "mn" a legal word?
        if(m != '1' && m != '2')
            return false;
        if(m == '2'){
            if(n > '6')
                return false;
        }
        return true;
    }
    int numDecodings(string s) {
        if(s.empty() || s[0]=='0')
            return 0;
        int* decodeWays = new int[s.size()];
        //be care of '0's
        decodeWays[0] = 1;
        for(int i=1; i<s.size(); i++){
            int ways = 0;
            if(s[i] > '0' && s[i] <= '9'){
                ways += decodeWays[i-1];
            }
            if(isLegalBiChar(s[i-1], s[i])){
                if(i<2)
                    ways += 1;
                else
                    ways += decodeWays[i-2];
            }
            decodeWays[i] = ways;
        }
        return decodeWays[s.size()-1];
    }
};

 

Categories
不学无术

LeetCode 64. Minimum Path Sum

思路

动态规划的问题,某一格的状态由它左侧及上侧的状态决定,即
[latex]minVal[x][y] = min( minVal[x-1][y], minVal[x][y-1] )[/latex]
当然了,第一行只由左侧决定,第一列只由上侧决定。

代码

class Solution {
public:
    int min(int x, int y){
        return x<y ? x : y;
    }
    int minPathSum(vector<vector<int>>& grid) {
        const int m = grid.size();
        if(m == 0)
            return 0;
        const int n = grid[0].size();
        for(int i=0; i<m; i++){
            for(int j=0; j<n; j++){
                if(i==0 && j==0)
                    continue;
                if(i == 0){
                    // min[0][i] = grid[0][i] + min[0][i-1]
                    grid[i][j] += grid[i][j-1];
                } else if(j == 0){
                    grid[i][j] += grid[i-1][j];
                } else {
                    grid[i][j] += min(grid[i-1][j], grid[i][j-1]);
                }
            }
        }
        return grid[m-1][n-1];
    }
};

 

Categories
不学无术

LeetCode 139. Word Break

Given a string s and a dictionary of words dict, determine if s can be segmented into a space-separated sequence of one or more dictionary words.
For example, given
s = "leetcode",
dict = ["leet", "code"].
Return true because "leetcode" can be segmented as "leet code".

思路

字符串处理的一个题目,首先想到的办法就是穷举:从头开始找N-letter word,查一下在不在字典里呗。但是纯穷举的话显然就会超时,因为这个问题可以分解成下面的子问题,即给定字符串s及起始位置start,寻找s[start…end]能不能够被拆分。这个子问题在暴力搜索的时候很容易被重复,所以得找个东西记录一下状态,碰到相同的子问题就不用再去查字典了。
所以,他是个动态规划的题。

代码

bool wordBreakSub(const string& s, unordered_set<string>& wordDict, int startPos, int* memory)
{
    if(memory[startPos] != -1)
        return memory[startPos]!= 0;
    string subStr;
    bool canBreak = false;
    for(int len=1; len <= s.size() - startPos; len++)
    {
        subStr = s.substr(startPos, len);
        if(wordDict.count(subStr) > 0) {
            if (startPos + len == s.size()) {
                memory[startPos] = 1;
                return true;
            }
            canBreak = wordBreakSub(s, wordDict, startPos + len, memory);
        }
        if(canBreak)
            break;
    }
    memory[startPos] = canBreak ? 1 : 0;
    return canBreak;
}
bool wordBreak(string s, unordered_set<string>& wordDict)
{
    int* memory = new int[s.size()];
    for(int i=0; i<s.size(); i++)
        memory[i] = -1; //-1:untest; 0-false; 1-true
    return wordBreakSub(s, wordDict, 0, memory);
}

 

Categories
木有技术

LeetCode 62. Unique Paths

原题:https://leetcode.com/problems/unique-paths/
robot is located at the top-left corner of a m x n grid (marked ‘Start’ in the diagram below).
The robot can only move either down or right at any point in time. The robot is trying to reach the bottom-right corner of the grid (marked ‘Finish’ in the diagram below).
How many possible unique paths are there?

Above is a 3 x 7 grid. How many possible unique paths are there?
Note: m and n will be at most 100.

思路

这是个经典的题目,反正就是动态规划的思路?说是动态规划,其实就是下一个状态怎么来的问题。此处的下一状态就是一个格子solution[m][n]他的结果是怎么来的问题,由于限定只能往右或者往下走,所以这个格子的走法无非是 从(m,n-1)和(m-1,n)走过来两种,只要把这两个状态的结果相加就是当前总的走法了。

解法

复杂度应该是O(m*n),做的时候在左侧和上侧加了个0边便于计算

public class Solution {
    public int uniquePaths(int m, int n) {
        int solutions[][] = new int[m+1][n+1];
        solutions[1][1]=1;
        for(int i=1; i<=m; i++){
            for(int j=1; j<=n; j++){
                if(i==1 && j==1)
                    continue;
                solutions[i][j] = solutions[i][j-1] + solutions[i-1][j];    // Comes from left, or upper side
            }
        }
        return solutions[m][n];
    }
}

 

Categories
不学无术

LeetCode 139. Word Break

题目

Given a string s and a dictionary of words dict, determine if s can be segmented into a space-separated sequence of one or more dictionary words.
For example, given
s = "leetcode",
dict = ["leet", "code"].
Return true because "leetcode" can be segmented as "leet code".

思路1

写的第一个版本是超时的,但是思路应当是正确的,反正就是动态规划,但我用的是递归方法。
就是如果找到s[start,end]是单词表中的单词时,有两种处理,一种是算入然后往后走;另一种是忽略这次匹配成功,继续往后寻找。
代码大概这样:

class Solution {
public:
	int findNextWord(const string& s, int start, int curr, unordered_set<string>& wordDict) {
		//Find the end position of next possible word, or return -1
		string word = s.substr(start, curr - start + 1);
		for (int i = curr + 1; i < s.size(); i++) {
			word += s[i];
			if (wordDict.count(word) > 0) {
				return i;
			}
		}
		return -1;
	}
	bool parseSeq(const string& s, unordered_set<string>& wordDict, int start, int end, map<int, bool>& cache) {
		//Judge s[start...end]
		if (end >= s.size())
			return false;
		if (cache.count(start) > 1)
			return cache[start];
		string word = s.substr(start, end - start + 1);
		if (wordDict.count(word) > 0) {
			bool withThisWord = parseSeq(s, wordDict, end + 1, end + 1, cache);
			if (end + 1 == s.size() || withThisWord) {
				cache[start] = true;
				return true;
			}
		}
		int nextPos = findNextWord(s, start, end, wordDict);
		if (-1 != nextPos) {
			bool appendWord = parseSeq(s, wordDict, start, nextPos, cache);
			if (appendWord) {
				cache[start] = true;
				return true;
			}
		}
		cache[start] = false;
		return false;
	}
	bool wordBreak(string s, unordered_set<string>& wordDict) {
		map<int, bool> cache;
		return parseSeq(s, wordDict, 0, 0, cache);
	}
};

但是如果这里出现了超级多的单字,比如字符串是

aaaaaaaaaaaaaaaaaaaaaaa

然后字典是

{a,aa,aaa,aaaa}

这就搞笑了,得匹配死,所以肯定是超时。

思路2

这个是讨论组里看来的,突然发现一般来说DP递归能做的,就可以从后往前用非递归的方法写。对于这个问题来说,从后往前并不像做LCS那样时要完全倒着写。举个例子,有字符串str=thisisawesome 和字典{this, is, a, we, some, awesome} 的时候,可以用一个缓存cache来记录是否在str[i]位置有单词结束,比如str[3]位置就是”this”这个词结束的地方。当顺着继续搜索的时候,往前查找缓存记录,可以知道如果cache[i]=true的话,那才有可能从i这个位置往后到当前位置组成新单词,接着去查找字典看看有没有这个单词,如果没有的话,就得继续往前找到新的可用的开头i。这里加黑的“才有可能”是比较关键的。
代码大概是这样

bool wordBreak(string s, unordered_set<string>& wordDict) {
		if (wordDict.empty())
			return false;
		vector<bool> cache(s.size() + 1, false); //If a word ends at i, cache[i] = true
		cache[0] = true;
		for (int i = 0; i <= s.size(); i++) {
			for (int j = i - 1; j >= 0; j--) {
				if (cache[j]) {
					//Look backward and start at the end place of previous word
					string word = s.substr(j, i - j); // s[j...i-1]
					if (wordDict.find(word) != wordDict.end()) {
						cache[i] = true;
						break;
					}
				}
			}
		}
		return cache[s.size()];
	}

这里面向前迭代不断寻找cache[i]并尝试的情况,对应于之前思路1里面找到可用单词后的决定是否使用的分支情况。但是由于减少了一大堆递归调用,所以实际情况下的复杂度降低了不少。

Categories
不学无术

LeetCode 322. Coin Change

You are given coins of different denominations and a total amount of money amount. Write a function to compute the fewest number of coins that you need to make up that amount. If that amount of money cannot be made up by any combination of the coins, return -1.
Example 1:
coins = [1, 2, 5], amount = 11
return 3 (11 = 5 + 5 + 1)
Example 2:
coins = [2], amount = 3
return -1.
Note:
You may assume that you have an infinite number of each kind of coin.

思路

动态规划的问题,不过如果倒着做的话,似乎会超时…
所以考虑往前递推的情况,即当前要解决X元的找零问题的最佳解法且已知<X时的解法,手头有一堆面值为coins[i]的硬币的时候,对于每种可用的硬币我们要做的选择是:

  • 尝试使用coin[i],即当前的最优结果=(X-coins[i])元时的最优结果+1,这个1就是用了coins[i]这个硬币;
  • 保持现有结果不变

这两个方法当然是取最好的存下来了。

代码

class Solution {
public:
    int MAX_NUM = 65535;
    int coinChange(vector<int>& coins, int amount) {
        vector<int> solutions(amount+1, MAX_NUM); //0~amount,共amount+1个
        solutions[0] = 0;
        int coinSz = coins.size();
        for(int i=1; i<=amount; i++){
            for(int c=0; c<coinSz; c++){
                //得到当前的amount,要么就是用原有的结果,要么就是使用amount[i-coin] + 1个当前面值的
                if(coins[c] <= i){
                    solutions[i] = min(solutions[i], 1 + solutions[i-coins[c]]);
                }
            }
        }
        if(solutions[amount] == MAX_NUM)
            return -1;
        return solutions[amount];
    }
};

 

Categories
不学无术

M$ 2016 题目3 : Demo Day

时间限制:10000ms
单点时限:1000ms
内存限制:256MB

描述

You work as an intern at a robotics startup. Today is your company’s demo day. During the demo your company’s robot will be put in a maze and without any information about the maze, it should be able to find a way out.
The maze consists of N * M grids. Each grid is either empty(represented by ‘.’) or blocked by an obstacle(represented by ‘b’). The robot will be release at the top left corner and the exit is at the bottom right corner.
Unfortunately some sensors on the robot go crazy just before the demo starts. As a result, the robot can only repeats two operations alternatively: keep moving to the right until it can’t and keep moving to the bottom until it can’t. At the beginning, the robot keeps moving to the right.

rrrrbb..
...r....     ====> The robot route with broken sensors is marked by 'r'.
...rrb..
...bb...

While the FTEs(full-time employees) are busy working on the sensors, you try to save the demo day by rearranging the maze in such a way that even with the broken sensors the robot can reach the exit successfully. You can change a grid from empty to blocked and vice versa. So as not to arouse suspision, you want to change as few grids as possible. What is the mininum number?

输入

Line 1: N, M.
Line 2-N+1: the N * M maze.
For 20% of the data, N * M <= 16.
For 50% of the data, 1 <= N, M <= 8.
For 100% of the data, 1<= N, M <= 100.

输出

The minimum number of grids to be changed.

注记:

动态规划的一道题,后悔当时没有看通过率先做了第二道,第二题没有熟练掌握Trie结果处理超时了!
这题就是状态麻烦点,我的处理方式是在当前位置就判断右侧及下侧的通行情况,必要时做出改变,然后继续。因此注意点是一开始就被[0][0]位置blocked的情况。
以(x位置,y位置,当前方向)为一个三元组,可以保存唯一的状态结果,避免重复递归。

代码:

未经详细测试,待测试!感觉自己的编码能力还是有限,欢迎过客提出意见。

#include <iostream>
#include <vector>
#include <string>
using namespace std;
const int FAIL = -32767;
struct St {
	bool hasRight;
	bool hasDown;
	int rightBest;
	int downBest;
	St(){
		hasRight = false;
		hasDown = false;
	}
	void setVal(bool movR, int val) {
		if (movR) {
			hasRight = true;
			rightBest = val;
		}
		else {
			hasDown = true;
			downBest = val;
		}
	}
	bool hasVal(bool movR) {
		if (movR)
			return hasRight;
		else
			return hasDown;
	}
	int getVal(bool movR) {
		if (movR)
			return rightBest;
		else
			return downBest;
	}
};
int legalMin(int x, int y) {
	if (x < 0) return y;
	else if (y < 0) return x;
	else return x<y ? x : y;
}
int moveMaze(const vector<string>& maze, int i, int j, bool movR, vector<vector<St>>& storedMap) {
	//cout << '.';
	if ((i == maze.size() - 1) && (j == maze[0].size() - 1)) {
		//Out of maze and finished
		return 0;
	}
	else if ((i == maze.size()) || (j == maze[0].size())) {
		//Reach boundary
		return FAIL;
	}
	if (storedMap[i][j].hasVal(movR)) {
		//cout << "HIT!" << i << ", " << j << ", "<< movR << endl;
		return storedMap[i][j].getVal(movR);
	}
	int right = FAIL;
	int down = FAIL;
	if (movR) {
		//Moving towards right side
		if (j == maze[0].size() - 1 || maze[i][j + 1] == '.') {
			right = moveMaze(maze, i, j + 1, movR, storedMap);
			if (j == maze[0].size() - 1)
				down = moveMaze(maze, i + 1, j, false, storedMap);
			else
				down = 1 + moveMaze(maze, i + 1, j, false, storedMap);
		}
		else {
			//blocked
			right = 1 + moveMaze(maze, i, j + 1, movR, storedMap);
			if (i == maze.size() - 1 || maze[i + 1][j] == '.')
				down = moveMaze(maze, i + 1, j, false, storedMap);
			else
				down = 1 + moveMaze(maze, i + 1, j, false, storedMap);
		}
	}
	else {
		if (i == maze.size() - 1 || maze[i + 1][j] == '.') {
			//Move down safely
			down = moveMaze(maze, i + 1, j, movR, storedMap);
			if (i == maze.size() - 1)
				right = moveMaze(maze, i, j + 1, true, storedMap);
			else
				right = 1 + moveMaze(maze, i, j + 1, true, storedMap);
		}
		else {
			//blocked
			down = 1 + moveMaze(maze, i + 1, j, movR, storedMap);
			if (j == maze[0].size() - 1 || maze[i][j + 1] == '.')
				right = moveMaze(maze, i, j + 1, true, storedMap);
			else
				right = 1 + moveMaze(maze, i, j + 1, true, storedMap);
		}
	}
	int result = legalMin(down, right);
	storedMap[i][j].setVal(movR, result);
	return result;
}
int main() {
	int N, M;
	cin >> N >> M;
	vector<string> maze;
	for (int i = 0; i<N; i++) {
		string str;
		cin >> str;
		maze.push_back(str);
	}
	vector<vector<St>> storedMap;
	for (int i = 0; i < N; i++) {
		vector<St> line;
		for (int j = 0; j < M; j++) {
			line.push_back(St());
		}
		storedMap.push_back(line);
	}
	int result;
	if(maze[0][0] == '.')
		result = moveMaze(maze, 0, 0, true, storedMap);
	else //[0][0]就是'b'
		result = 1 + moveMaze(maze, 0, 0, true, storedMap);
	cout << result << endl;
	return 0;
}