算法题4

1. 课程表 II

现在你总共有 n 门课需要选,记为 0 到 n-1。

在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们: [0,1]

给定课程总量以及它们的先决条件,返回你为了学完所有课程所安排的学习顺序。

可能会有多个正确的顺序,你只要返回一种就可以了。如果不可能完成所有课程,返回一个空数组。

输入: 4, [[1,0],[2,0],[3,1],[3,2]]
输出: [0,1,2,3] or [0,2,1,3]
解释: 总共有 4 门课程。要学习课程 3,你应该先完成课程 1 和课程 2。并且课程 1 和课程 2 都应该排在课程 0 之后。
因此,一个正确的课程顺序是 [0,1,2,3] 。另一个正确的排序是 [0,2,1,3] 。

分析:
构建一个edges与redges邻接矩阵,若edges[i]为空则表示i可以学习加入队列queue中。依次从队列queue中取出i,放入result中,然后再redges中找出依赖i的节点j,在edges[j]中删除i,若此时edges[j]为空,则将其加入queue。直到queue为空,此时result.size()=n节点数,表示可以学习完,若不等于表示不可学习。

vector<int> findOrder(int numCourses, vector<pair<int, int>>& prerequisites) {//课程表 II
	vector<unordered_set<int>> edges(numCourses, unordered_set<int>());
	vector<unordered_set<int>> redges(numCourses, unordered_set<int>());
	for (auto edge : prerequisites) {
		edges[edge.first].insert(edge.second);
		redges[edge.second].insert(edge.first);
	}
	queue<int> qe;
	vector<int> result;
	for (int i = 0; i < numCourses; ++i) {
		if (edges[i].size() == 0) {
			qe.push(i);
		}
	}
	while (!qe.empty()){
		int tem = qe.front();
		qe.pop();
		result.push_back(tem);
		for (auto end : redges[tem]) {
			edges[end].erase(tem);
			if (edges[end].size() == 0) {
				qe.push(end);
			}
		}
	}
	if (result.size() == numCourses) {
		return result;
	}
	else {
		return {};
	}
}

2. 二叉树展开为链表

给定一个二叉树,原地将它展开为链表。

例如,给定二叉树
1
/ \
2 5
/ \ \
3 4 6

1
\
2
\
3
\
4
\
5
\
6

分析:

后序遍历,先找到最左节点,然后将该节点的左孩子放到右孩子处,将右孩子放到左孩子的右孩子。在处理右孩子时,需要找到左孩子最右的节点。

void flatten(TreeNode* root) {//二叉树展开为链表
	if (root == NULL) return;
	flatten(root->left);
	flatten(root->right);
	TreeNode *tem = root->right;
	root->right = root->left;
	root->left = NULL;
	while (root->right)
	{
		root = root->right;
	}
	root->right = tem;
}

3. 解码方法

一条包含字母 A-Z 的消息通过以下方式进行了编码:

‘A’ -> 1
‘B’ -> 2

‘Z’ -> 26
给定一个只包含数字的非空字符串,请计算解码方法的总数。

分析:动态规划。
i位置判断能跟i-1组成字母,dp[i]+=dp[i-2]或1
i位置判断能自己组成字母,dp[i]+=dp[i-1]或1

bool isLetter(string s) {
	int num = atoi(s.c_str());
	if (s.size() == 2 && s[0] == '0') return false;
	if (num > 26 || num < 1) {
		return false;
	}
	else {
		return true;
	}
}
int numDecodings(string s) {//解码方法
	int n = s.size();
	if (s[0] == '0') return 0;
	vector<int> help(n, 0);
	for (int i = 0; i < n; ++i) {
		if (i - 1 >= 0) {
			string cur = "";
			cur += s[i - 1];
			cur += s[i];
			if (isLetter(cur)) {
				if (i - 2 >= 0) {
					help[i] += help[i - 2];
				}
				else {
					help[i] = 1;
				}
			}
		}
		string cur = "";
		cur += s[i];
		if (isLetter(cur)) {
			if (i - 1 >= 0) {
				help[i] += help[i - 1];
			}
			else {
				help[i] = 1;
			}
		}
	}
	return help[n - 1];
}

4. 单词拆分

给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。

说明:

拆分时可以重复使用字典中的单词。
你可以假设字典中没有重复的单词。

输入: s = “catsandog”, wordDict = [“cats”, “dog”, “sand”, “and”, “cat”]
输出: false

输入: s = “applepenapple”, wordDict = [“apple”, “pen”]
输出: true
解释: 返回 true 因为 “applepenapple” 可以被拆分成 “apple pen apple”。
注意你可以重复使用字典中的单词。

分析:
动态规划,使用一个help[0]=-1数组,保存到当前位置i能够拆分的i。若i与help中的任意一个index后的string在wordDict中,则将i加入help。若help最后一位为有n-1,则返回true,否则false。

bool wordBreak(string s, vector<string>& wordDict) {//单词拆分
	int n = s.size();
	vector<int> help;
	help.push_back(-1);
	for (int i = 0; i < n; ++i) {
		int flag = 0;
		for (auto index : help) {
			string cur = s.substr(index + 1, i - index);
			for (auto key : wordDict) {
				if (key == cur) {
					help.push_back(i);
					flag = 1;
					break;
				}
			}
			if (flag == 1) {
				break;
			}
		}
	}
	if (*(help.end() - 1) == n - 1) return true;
	return false;
}

5. 乘积最大子序列

给定一个整数数组 nums ,找出一个序列中乘积最大的连续子序列(该序列至少包含一个数)。

输入: [-2,0,-1]
输出: 0
解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。

输入: [2,3,-2,4]
输出: 6
解释: 子数组 [2,3] 有最大乘积 6。

分析:
动态规划,但是正负号很难处理,因此用两个dp,一个保存当前位置乘积最小子序列(可能是自身),另一个保存当前位置乘积最大子序列(可能是自身)。每次选出当前位置与当上一位置dp最大最小值相乘结果的最大最小值保存。

int maxProduct(vector<int>& nums) {
	int lastMax = nums[0];
	int lastMin = nums[0];
	int result = nums[0];
	for (int i = 1; i < nums.size(); ++i) {
		int temMax = lastMax;
		int temMin = lastMin;
		lastMax = std::max(nums[i], std::max(nums[i] * temMax, nums[i] * temMin));
		lastMin = std::min(nums[i], std::min(nums[i] * temMax, nums[i] * temMin));
		result = std::max(result, lastMax);
	}
	return result;
}

6. 最大正方形

在一个由 0 和 1 组成的二维矩阵内,找到只包含 1 的最大正方形,并返回其面积。

输入:
1 0 1 0 0
1 0 1 1 1
1 1 1 1 1
1 0 0 1 0
输出: 4

分析:动态规划,当matrix[i][j]==1时,dp[i][j]为min{dp[i-1][j],dp[i][j-1]dp[i-1][j-1]}+1。上或左或左上中的最小值+1。

int maximalSquare(vector<vector<char>>& matrix) {//最大正方形
	int n = matrix.size();
	if (n == 0) return 0;
	int m = matrix[0].size();
	vector<vector<int>> dp(n + 1, vector<int>(m + 1, 0));
	int result = 0;
	for (int i = 1; i <= n; ++i) {
		for (int j = 1; j <= m; ++j) {
			if (matrix[i-1][j-1] == '1') {
				dp[i][j] = std::min(dp[i - 1][j], std::min(dp[i][j - 1], dp[i - 1][j - 1])) + 1;
				if (dp[i][j] > result) {
					result = dp[i][j];
				}
			}
		}
	}
	return result*result;
}

7. 只有两个键的键盘

最初在一个记事本上只有一个字符 ‘A’。你每次可以对这个记事本进行两种操作:

Copy All (复制全部) : 你可以复制这个记事本中的所有字符(部分的复制是不允许的)。
Paste (粘贴) : 你可以粘贴你上一次复制的字符。
给定一个数字 n 。你需要使用最少的操作次数,在记事本中打印出恰好 n 个 ‘A’。输出能够打印出 n 个 ‘A’ 的最少操作次数。

输入: 3
输出: 3
解释:
最初, 我们只有一个字符 ‘A’。
第 1 步, 我们使用 Copy All 操作。
第 2 步, 我们使用 Paste 操作来获得 ‘AA’。
第 3 步, 我们使用 Paste 操作来获得 ‘AAA’。

分析:这里没有插入一个A的操作。当想写i个A时,只能找之前能够i%j==0的j,然后复制粘贴,粘贴的次数为i/j-1,加上一次复制为i/j。

int minSteps(int n) {//只有两个键的键盘
	if (n == 1) return 0;
	if (n < 6) return n;
	vector<int> dp(n + 1, n);
    dp[1]=0;
	for (int i = 2; i <= n; ++i) {
		for (int j = 1; j <= (i / 2); ++j) {
			if(i%j==0){
				dp[i] = std::min(dp[i], dp[j] + i / j);
			}
		}
	}
	return dp[n];
}

8. 最佳买卖股票时机含冷冻期

给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。

设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):

你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。

输入: [1,2,3,0,2]
输出: 3
解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出]

分析:本题共有三个状态,以后的题注意区分状态。[buy,sell,rest]。可以写出状态转移方程

buy[i] = max{rest[i-1]-price[i], buy[i-1]}  
sell[i] = max{buy[i-1]+price[i], sell[i-1]}  
rest[i] = max{sell[i-1], rest[i-1]}

又由于冷却状态时,rest[i] = sell[i-1]由此可简化上式

buy[i] = max{sell[i-2]-price[i], buy[i-1]}  
sell[i] = max{buy[i-1]+price[i], sell[i-1]}  
int maxProfit(vector<int>& prices) {//最佳买卖股票时机含冷冻期
	int n = prices.size();
	if (n < 2) return 0;
	if (n == 2) return std::max(0, prices[1] - prices[0]);
	vector<int> buy(n, 0);
	vector<int> sell(n, 0);
	buy[0] = -prices[0];
	buy[1] = std::max(buy[0], -prices[1]);
	sell[1] = std::max(sell[0], buy[0] + prices[1]);
	for (int i = 2; i < n; ++i) {
		buy[i] = std::max(buy[i - 1], sell[i - 2] - prices[i]);
		sell[i] = std::max(buy[i - 1] + prices[i], sell[i - 1]);
	}
	return sell[n - 1];
}

9. 戳气球

有 n 个气球,编号为0 到 n-1,每个气球上都标有一个数字,这些数字存在数组 nums 中。

现在要求你戳破所有的气球。每当你戳破一个气球 i 时,你可以获得 nums[left] * nums[i] * nums[right] 个硬币。 这里的 left 和 right 代表和 i 相邻的两个气球的序号。注意当你戳破了气球 i 后,气球 left 和气球 right 就变成了相邻的气球。

求所能获得硬币的最大数量。

说明:

你可以假设 nums[-1] = nums[n] = 1,但注意它们不是真实存在的所以并不能被戳破。
0 ≤ n ≤ 500, 0 ≤ nums[i] ≤ 100

输入: [3,1,5,8]
输出: 167
解释: nums = [3,1,5,8] --> [3,5,8] --> [3,8] --> [8] --> []
coins = 3*1*5 + 3*5*8 + 1*3*8 + 1*8*1 = 167

分析:
每一次最后一次运算为为i乘以最左边leftMax与最右边rightMax的值。因此可以使用逆序来将nums[]分为左右两边来处理。
用动态规划+分而治之来处理,dp[left][right]等于left到
right之间任意一个i与left和right相乘的值加上i到dp[left][i]+dp[i][right]。因此先从最小间距2开始一直到n-1进行如上迭代。

10. 最大的二维点集,右上角没有点的点集。

P为给定的二维平面整数点集。定义 P 中某点x,如果x满足 P 中任意点都不在 x 的右上方区域内(横纵坐标都大于x),则称其为“最大的”。求出所有“最大的”点的集合。(所有点的横坐标和纵坐标都不重复, 坐标轴范围在[0, 1e9) 内)

如下图:实心点为满足条件的点的集合。请实现代码找到集合 P 中的所有 ”最大“ 点的集合并输出。
微信截图_20180909215343.png

分析:可以将x按照降序排序,将y按照升序排序。然后从前往后依次比较y,若 y i &gt; max ⁡ y_i&gt;\max yi>max则满足条件,然后令 max ⁡ = y i \max=y_i max=yi

11. 判断一个数字是否为回文数

每次取第一位跟最后一位比较是否相同,采用除或取余操作。

12. 给定N个整数组成的序列,编程计算该序列的最优M段分割,使M段子序列的和的最大值达到最小

动态规划:
构造一个矩阵dp[n][m],dp[i][j]表示索引i及其之前的数分成j组的最大值。因此有如下状态转移方程:
j = 1 : d p [ i ] [ 1 ] = d p [ i − 1 ] [ 1 ] + n u m [ i ] j ≠ 1 : d p [ i ] [ j ] = m i n { m a x { d p [ i ] [ 1 ] − d p [ k ] [ 1 ] , d p [ k ] [ j − 1 ] } } j=1: dp[i][1] = dp[i-1][1] + num[i]\\ j\neq 1: dp[i][j] = min\{max\{dp[i][1] - dp[k][1],dp[k][j-1]\}\} j=1:dp[i][1]=dp[i1][1]+num[i]j̸=1:dp[i][j]=min{max{dp[i][1]dp[k][1],dp[k][j1]}}
dp[i][1]-dp[k][1]可以理解为将k+1~i分成第j组,然后与前k个数组成j-1组的最大值比较取大。然后遍历所有可能的k,取最小值。

13. LRU缓存机制

运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put 。

获取数据 get(key) - 如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。
写入数据 put(key, value) - 如果密钥不存在,则写入其数据值。当缓存容量达到上限时,它应该在写入新数据之前删除最近最少使用的数据值,从而为新的数据值留出空间。

LRUCache cache = new LRUCache( 2 /* 缓存容量 */ );
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // 返回 1
cache.put(3, 3); // 该操作会使得密钥 2 作废
cache.get(2); // 返回 -1 (未找到)
cache.put(4, 4); // 该操作会使得密钥 1 作废
cache.get(1); // 返回 -1 (未找到)
cache.get(3); // 返回 3
cache.get(4); // 返回 4

分析:
为快速添加和删除,我们需要使用链表来设计cache,链表中从头到尾的数据顺序依次是:(最近访问)->…(最旧访问);
因为是最近访问的在链表的最前,我们使用list来保存key值,而使用hashmap来保存(key,value)对即可,同时需要对value进行改造,因为我们还需要将每一个value与list中的key结点进行绑定,组成pair,以便每次操作进行list的最近访问的调整;

添加节点put(key, value):

判断hashmap中是否存在key节点,如果存在则调整List,将key置为头部(最近访问),更新hashmap指向key节点的指针;
如果不存在key值对,则判断capacity是否满容,若不满容量则直接在list和hashmap中加入新节点(list在表头添加);否则则删除list末尾key和hashmap中的对应值对,再进行添加。
新节点插入到表头即可,时间复杂度O(1)。

查找节点get(key):

在hashmap中查询,若不存在则返回-1;
若存在,则调整list位置更新hashmap对应指针,返回key对应的value;
每次节点被查询到时,将节点移动到链表头部,时间复杂度O(1)。

class LRUCache {
public:
	LRUCache(int capacity) {
		_capacity = capacity;
	}

	int get(int key) {
		auto it = cache.find(key);
		if (it == cache.end()) {//不在缓存中
			return -1;
		}
		else {//在缓存中
			adjust(it);//更新key在链表中的位置
			return it->second.first;;
		}
	}

	void put(int key, int value) {//判断是否在,若不在,判断是否满,如果满了删了再加。若在,则提前
		auto it = cache.find(key);
		if (it == cache.end()) {//不在
			if (cache.size() < _capacity) {//不满直接加
				used.push_front(key);
				valListPair tem;
				tem.first = value;
				tem.second = used.begin();
				cache[key] = tem;
			}
			else {
				cache.erase(used.back());//map可以直接删key
				used.pop_back();
				used.push_front(key);
				cache[key] = { value,used.begin() };
			}
		}
		else{//key在缓存中,只需要调整位置
			adjust(it);//将key调整到链表的最前端
			it->second.first = value;//更新key对应的value
		}
	}
private:
	int _capacity;
	typedef pair<int, list<int>::iterator> valListPair;
	typedef unordered_map<int, valListPair> keyValMap;
	list<int> used;
	keyValMap cache;
	void adjust(keyValMap::iterator it) {//将命中的key移到链表的最前面
		int key = it->first;
		used.erase(it->second.second);
		used.push_front(key);
		it->second.second = used.begin();
	}
};

14. 接雨水

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
微信截图_20181017220404.png
上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。 感谢 Marcos 贡献此图。

分析:设计左右指针lindex,rindex,保存左右指针遇到过的最大值lmax,rmax。左右指针同时向中间移动,若lmax<rmax,water = lmax-high[lindex];lindex++,否则water=rmax-high[rindex];rindex–。

int trap(vector<int>& height) {//接雨水
	if (height.size() <= 1) return 0;
	int lindex = 0;
	int rindex = height.size() - 1;
	int lmax = 0;
	int rmax = 0;
	int result = 0;
	while (lindex<=rindex)
	{
		lmax = std::max(height[lindex], lmax);
		rmax = std::max(height[rindex], rmax);
		if (lmax <= rmax) {
			result += lmax - height[lindex];
			lindex++;
		}
		else {
			result += rmax - height[rindex];
			rindex--;
		}
	}
	return result;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
本书带完整书签 第1章 基础 1 1.1 基础编程模型 4 1.1.1 Java程序的基本结构 4 1.1.2 原始数据类塑与表达式 6 1.1.3 语句 8 1.1.4 简便记法 9 1.1.5 数组 10 1.1.6 静态方法 12 1.1.7 API 16 1.1.8 字符串 20 1.1.9 输入输出 21 1.1.10 二分査找 28 1.1.11 展望 30 1.2 数据抽象 38 1.2.1 使用抽象数据类型 38 1.2.2 抽象数据类型举例 45 1.2.3 抽象教据类型的实现 52 1.2.4 更多抽象数据类型的实现 55 1.2.5 数据类型的设计 60 1.3 背包、队列和栈 74 1.3.1 API 74 1.3.2 集合类數据类型的实现 81 1.3.3 链表 89 1.3.4 综述 98 1.4 算法分析 108 1.4.1 科学方法 108 1.4.2 观察 108 1.4.3 数学模型 112 1.4.4 增长数量级的分类 117 1.4.5 设计更快的算法 118 1.4.6 倍率实验 121 1.4.7 注意事项 123 1.4.8 处理对于输入的依赖 124 1.4.9 内存 126 1.4.10 展望 129 1.5 案例研究:union-find算法 136 1.5.1 动态连通性 136 1.5.2 实现 140 1.5.3 展望 148 第2章 排序 152 2.1 初级排序算法 153 2.1.1 游戏规则 153 2.1.2 选择排序 155 2.1.3 插入排序 157 2.1.4 排序算法的可视化 159 2.1.5 比较两种排序算法 159 2.1.6 希尔排序 162 2.2 归并排序 170 2.2.1 原地归并的抽象方法 170 2.2.2 自顶向下的归并排序 171 2.2.3 自底向上的归并排序 175 2.2.4 排序算法的复杂度 177 2.3 快速排序 182 2.3.1 基本算法 182 2.3.2 性能特点 185 2.3.3 算法改进 187 2.4 优先队列 195 2.4.1 API 195 2.4.2 初级实现 197 2.4.3 堆的定义 198 2.4.4 堆的算法 199 2.4.5 堆排序 205 2.5 应用 214 2.5.1 将各种數据排序 214 2.5.2 我应该使用啷种排序算法 218 2.5.3 问的归约 219 2.5.4 排序应用一览 221 第3章查找 227 3.1 符号表 228 3.1.1 API 228 3.1.2 有序符号表 230 3.1.3 用例举例 233 3.1.4 无序链表中的顺序查找 235 3.1.5 有序數组中的二分查找 238 3.1.6 对二分査找的分析 242 3.1.7 预览 244 3.2 二叉查找树 250 3.2.1 基本实现 250 3.2.2 分析 255 3.2.3 有序性相关的方法与删除操作 257 3.3 平衡査找树 269 3.3.1 2-3査找树 269 3.3.2 红黑二叉查找树 275 3.3.3 实现 280 3.3.4 删除操作 282 3.3.5 红黑树的性质 284 3.4 散列表 293 3.4.1 散列函数 293 3.4.2 基于拉链法的散列表 297 3.4.3 基于线性探测法的散列表 300 3.4.4 调整教组大小 304 3.4.5 内存使用 306 3.5 应用 312 3.5.1 我应该使用符号表的哪种实现 312 3.5.2 集合的API 313 3.5.3 字典类用例 315 3.5.4 索引类用例 318 3.5.5 稀疏向量 322 第4章 图 329 4.1 无向图 331 4.1.1 术语表 331 4.1.2 表示无向图的数据类型 333 4.1.3 深度优先搜索 338 4.1.4 寻找路径 342 4.1.5 广度优先搜索 344 4.1.6 连通分量 349 4.1.7 符号图 352 4.1.8 总结 358 4.2 有向图 364 4.2.1 术语 364 4.2.2 有向图的数据类型 365 4.2.3 有向图中的可达性 367 4.2.4 环和有向无环图 369 4.2.5 有向图中的强连通性 378 4.2.6 总结 385 4.3 最小生成树 390 4.3.1 原理- 391 4.3.2 加权无向图的数据类型 393 4.3.3 最小生成树的API和测试用例 396 4.3.4 Prim算法 398 4.3.5 Prim算法的即时实现 401 4.3.6 Kruskal算法 404 4.3.7 展望 407 4.4 最短路径 412 4.4.1 最短路径的性质 413 4.4.2 加权有向图的数据结构 414 4.4.3 最短路径算法的理论基础 420 4.4.4 Dijkstra算法 421 4.4.5 无环加权有向图中的最短路径算法 425 4.4.6 一般加权有向图中的最短路径问 433 4.4.7 展望 445 第5章 字符串 451 5.1 字符串排序 455 5.1.1 键索引计数法 455 5.1.2 低位优先的字符串排序 458 5.1.3 高位优先的字符串排序 461 5.1.4 三向字符串快速排序 467 5.1.5 字符串排序算法的选择 470 5.2 单词查找树 474 5.2.1 单词查找树 475 5.2.2 单词查找树的性质 483 5.2.3 三向单词查找树 485 5.2.4 三向单词查找树的性质 487 5.2.5 应该使用字符串符号表的哪种实现 489 5.3 子字符串查找 493 5.3.1 历史简介 493 5.3.2 暴力子字符串査找算法 494 5.3.3 Knuth-Morris-Pratt子字符串查找算法 496 5.3.4 Boyer-Moore字符串查找算法 502 5.3.5 Rabin-Karp指纹字符串查找算法 505 5.3.6 总结 509 5.4 正则表达式 514 5.4.1 使用正则表达式描述模式 514 5.4.2 缩略写法 516 5.4.3 正则表达式的实际应用 517 5.4.4 非确定有限状态自动机 518 5.4.5 模拟NFA的运行 520 5.4.6 构造与正则表达式对应的NFA 522 5.5 数据压缩 529 5.5.1 游戏规则 529 5.5.2 读写二进制数据 530 5.5.3 局限 533 5.5.4 热身运动:基因组 534 5.5.5 游程编码 537 5.5.6 霍夫曼压缩 540 第6章背景 558 索引 611
个人觉得是我见过的最简单易懂的算法入门书籍。 以前搜刮过几本算法竞赛书,但是难度终归太大【好吧,其实是自己太懒了】。 略翻过教材,大多数水校的教材,大家懂的。好一点的也是那本国内的经典,不是说它写的不好,只是没有这一本好。 本书Java实现,配有大量的图解,没有一句难懂的话,而且全都是模块化实现。 讲的都是实用算法,没有那些高大上听着名字就让人感到很害怕的东西,个人觉得比CLRS实用性要强,更加适合入门的学习。 大一,推荐这本书入门 【有C语言基础即可,自己去搜索下如何用Java写出Hello World就没有问】 大二,推荐这本书从头到尾好好读一遍,做下上千道的课后习 【后面的有点小难度,但是难度不大值得一做,听起来很多的样子,用心去做,相信很快就可以做完的】。 大三,推荐这本书,重新温习已知算法,为找工作,考研做准备。 【可以试着自己在纸上全部实现一遍】 大四,依旧推荐这本书,没事重温经典,当手册来查也不错。 Sedgwick 红黑树的发现者,Donald E.Knuth 的得意门生,对各种算法都有比较深入的研究,他的书,我想不会太差。 也许对于数据结构的学习涉及的内容比较少,没有动态规划,图论也只是讲了很基础的东西,字符串中KMP弄的过于复杂(对比于acm)。但是瑕不掩瑜,对于绝大部分内容真的讲的超级清楚,完美的图解,就像单步调试一样,也许是一本不需要智商就能看懂的算法书(习应该略有难度,还没有做,打算上Princeton的公开课时同步跟进)。至少这是一本让我这个算法渣渣看了爱不释手,怦然心动的书。 完美学习资源: 官方主页:http://algs4.cs.princeton.edu/home/ Coursera公开课:https://www.coursera.org/course/algs4partI (听说已经开课两期了,最近即将开课的时间是2014/09/05号那期,希望有兴趣的同学一起来学习)。 MOOC平台(笔记、讨论等): http://mooc.guokr.com/course/404/Algorithms--Part-I/ http://mooc.guokr.com/course/403/Algorithms--Part-II/ 不得不吐槽,他的lecture比他的书好,他本人讲的课更是一绝。 互补课程: 斯福坦的Algorithms: Design and Analysis, http://mooc.guokr.com/course/157/Algorithms--Design-and-Analysis--Part-1/ 快毕业了才接触到豆瓣和MOOC,看到很多经典的书籍都是推荐大学一二年级的学生看,每每想到自己却连书皮都没有摸过,就深感惭愧。 我们都老的太快,却聪明得太迟。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值