├── docs └── RandomListNode_solution2.png ├── Algorithm.csproj ├── .gitignore ├── InterviewGuideProgram.cs ├── Program.cs ├── CommonProgram.cs ├── 剑指offer ├── Sum.cs ├── PrintNumbers.cs ├── JumpFloor.cs ├── RectCover.cs ├── ReplaceSpace.cs ├── FirstNotRepeatingChar.cs ├── Add.cs ├── IsPopOrder.cs ├── Multiply.cs ├── Find.cs ├── FindKthToTail.cs ├── DeleteNode.cs ├── SimulateQueueWithStack.cs ├── PrintMinNumber.cs ├── StackWithMin.cs ├── HasSubtree.cs ├── GetUglyNumber.cs ├── NumberOf1.cs ├── LastRemaining.cs ├── PrintFromTopToBottom.cs ├── GetNumberOfK.cs ├── IsBalanced.cs ├── ReverseList.cs ├── MovingCount.cs ├── IsNumeric.cs ├── LeftRotateString.cs ├── ReOrderArray.cs ├── NumberOf1Between1AndN.cs ├── DeleteDuplication.cs ├── FindGreatestSumOfSubArray.cs ├── FindPath.cs ├── GetNext.cs ├── TreeDepth.cs ├── HasPath.cs ├── FirstAppearingOnce.cs ├── VerifySquenceOfBST.cs ├── InversePairs.cs ├── JumpFloorII.cs ├── MinNumberInRotateArray.cs ├── StrToInt.cs ├── MaxInWindows.cs ├── Merge.cs ├── Mirror.cs ├── PrintListFromTailToHead.cs ├── PrintMatrix.cs ├── Duplicate.cs ├── KthNode.cs ├── FindNumsAppearOnce.cs ├── FindContinuousSequence.cs ├── GetLeastNumbers.cs ├── PrintTree2.cs ├── RandomListNode.cs ├── FindNumbersWithSum.cs ├── ReverseSentence.cs ├── CutRope.cs ├── GetMedian.cs ├── Power.cs └── MoreThanHalfNum.cs ├── SortProgram.cs ├── 程序员代码面试指南 ├── NumberOf1From1ToN.cs └── PrintMinString.cs ├── Leetcode ├── SpiralOrder.cs ├── ContainsDuplicate.cs ├── AddStrings.cs ├── ProductExceptSelf.cs ├── IsValidParentheses.cs ├── MaxArea.cs ├── ReverseString.cs ├── RectangleOverlap.cs ├── ReverseWords.cs ├── TwoSum.cs ├── ThreeSum.cs ├── RemoveDuplicates.cs ├── FindMedianSortedArrays.cs ├── LongestPalindrome.cs └── ThreeSumClosest.cs ├── 排序算法 ├── SimpleInsertionSort.cs ├── CountSort.cs ├── ShellSort.cs ├── SimpleSelectionSort.cs ├── BubbleSort.cs ├── BucketSort.cs ├── MergeSort.cs ├── QuickSort.cs └── HeapSort.cs ├── LeetcodeProgram.cs └── 常用算法 └── KMP.cs /docs/RandomListNode_solution2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwiniwin/Algorithm/HEAD/docs/RandomListNode_solution2.png -------------------------------------------------------------------------------- /Algorithm.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.2 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #////////////////////////// 2 | # Build results 3 | #////////////////////////// 4 | 5 | [Dd]ebug/ 6 | [Rr]elease/ 7 | x64/ 8 | [Bb]in/ 9 | [Oo]bj/ 10 | 11 | #////////////////////////// 12 | # VS Code 13 | #////////////////////////// 14 | 15 | .vscode/ 16 | -------------------------------------------------------------------------------- /InterviewGuideProgram.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Algorithm 4 | { 5 | /// 6 | /// 程序员代码面试指南 模块 7 | /// 8 | class InterviewGuideProgram 9 | { 10 | public static void Test() 11 | { 12 | // 1到n中1的出现次数 13 | // NumberOf1From1ToN.Solution.Test(); 14 | 15 | // 拼接所有的字符串产生字典序最小的字符串 16 | // PrintMinString.Solution.Test(); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Algorithm 4 | { 5 | class Program 6 | { 7 | static void Main(string[] args) 8 | { 9 | // CodingInterviewsProgram.Test(); // 剑指offer 10 | LeetcodeProgram.Test(); // Leetcode 11 | // SortProgram.Test(); // 排序算法 12 | // InterviewGuideProgram.Test(); // 程序员代码面试指南 13 | // CommonProgram.Test(); // 常用算法 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /CommonProgram.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Algorithm 4 | { 5 | /// 6 | /// 常用算法 模块 7 | /// 8 | class CommonProgram 9 | { 10 | public static void Test() 11 | { 12 | 13 | // new KMP.Solution().Test(); // KMP模式匹配算法 14 | // new AStar.Solution().Test(); // A*寻路算法 15 | // new Huffman.Solution().Test(); // 哈夫曼算法 16 | new PointIn.Solution().Test(); // 判断点是否在某一区域内 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /剑指offer/Sum.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 求1+2+3+...+n 4 | 5 | 题目描述: 6 | 求1+2+3+...+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。 7 | 8 | 代码结构: 9 | class Solution 10 | { 11 | public int Sum_Solution(int n) 12 | { 13 | // write code here 14 | } 15 | } 16 | */ 17 | using System; 18 | namespace Sum { 19 | 20 | class Solution { 21 | 22 | /// 23 | /// 解法 24 | /// 基本思路: 25 | /// 使用递归代替循环,使用逻辑与的短路特性来终止递归 26 | /// 27 | 28 | public int Sum_Solution(int n) 29 | { 30 | bool ret = n > 1 && ((n +=Sum_Solution(n - 1)) > 1); 31 | return n; 32 | } 33 | 34 | 35 | public void Test() { 36 | 37 | int n = 3; 38 | n = 1; 39 | n = 5; 40 | 41 | Console.WriteLine(Sum_Solution(n)); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /剑指offer/PrintNumbers.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 打印从1到最大的n位数 4 | 5 | 题目描述: 6 | 输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数 999。 7 | 1. 用返回一个整数列表来代替打印 8 | 2. n 为正整数,0 < n <= 5 9 | 10 | 代码结构: 11 | class Solution { 12 | public List printNumbers (int n) { 13 | // write code here 14 | } 15 | } 16 | */ 17 | using System; 18 | using System.Collections.Generic; 19 | namespace PrintNumbers { 20 | class Solution { 21 | public List PrintNumbers (int n) { 22 | int num = 1; 23 | while(n -- > 0) { 24 | num *= 10; 25 | } 26 | 27 | List result = new List(num - 1); 28 | for(int i = 0; i < num - 1; i ++) { 29 | result.Add(i + 1); 30 | } 31 | return result; 32 | } 33 | 34 | public void Test() 35 | { 36 | int n = 1; 37 | foreach (var num in PrintNumbers(n)) 38 | { 39 | Console.Write(num + " "); 40 | } 41 | Console.WriteLine(); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /SortProgram.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Algorithm 4 | { 5 | /// 6 | /// 排序算法汇总 模块 7 | /// 所有排序算法均是以从小到大为例的 8 | /// 9 | class SortProgram 10 | { 11 | public static void Test() 12 | { 13 | // new BubbleSort.Solution().Test(); // 冒泡排序 14 | // new QuickSort.Solution().Test(); // 快速排序 15 | // new SimpleInsertionSort.Solution().Test(); // 简单插入排序 16 | // new ShellSort.Solution().Test(); // 希尔排序 17 | // new SimpleSelectionSort.Solution().Test(); // 简单选择排序 18 | // new HeapSort.Solution().Test(); // 堆排序 19 | // new MergeSort.Solution().Test(); // 归并排序 20 | // new BucketSort.Solution().Test(); // 桶排序 21 | new CountSort.Solution().Test(); // 计数排序 22 | // new RadixSort.Solution().Test(); // 基数排序 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /剑指offer/JumpFloor.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 跳台阶 4 | 5 | 题目描述: 6 | 一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。 7 | 8 | 代码结构: 9 | class Solution 10 | { 11 | public int JumpFloor(int number) 12 | { 13 | // write code here 14 | } 15 | } 16 | */ 17 | using System; 18 | namespace JumpFloor { 19 | 20 | class Solution { 21 | 22 | /// 23 | /// 解法 24 | /// 基本思路: 25 | /// 对于n级台阶,设一共有F(n)种跳法 26 | /// 第一次青蛙可以选择跳1级,则剩下的跳法就是F(n-1) 27 | /// 青蛙也可以选择跳2级,则剩下的跳法就是F(n-2) 28 | /// 即F(n) = F(n-1) + F(n-2),这不就是斐波那契数列嘛 29 | /// 所以求斐波那契数列的解法都可以用于这道题 详情可参考 Fibonacci.cs 文件 30 | /// 这里给出简单的递归解法,其余斐波那契数列解法不再赘述 31 | /// 32 | 33 | public int JumpFloor(int number) 34 | { 35 | if(number <= 2){ 36 | return number; 37 | } 38 | return JumpFloor(number - 1) + JumpFloor(number - 2); 39 | } 40 | 41 | public void Test() { 42 | 43 | int number = 0; 44 | number = 2; 45 | number = 3; 46 | number = 4; 47 | number = 39; 48 | 49 | Console.WriteLine(JumpFloor(number)); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /程序员代码面试指南/NumberOf1From1ToN.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 1到n中1的出现次数 4 | 5 | 题目描述: 6 | 给定一个整数n,返回从1到n的数字中1出现的个数。 7 | 例如: 8 | n=5,1∼n为1, 2, 3, 4, 5。1,2,3,4,5。那么1出现了1次,所以返回1。 9 | n=11,1∼n为1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11。 10 | 1,2,3,4,5,6,7,8,9,10,11。那么1出现的次数为1(出现1次),10(出现1次),11(有两个1,所以出现了2次),所以返回4。 11 | 12 | 输入描述: 13 | 输入一个整数N。 14 | 15 | 输出描述: 16 | 输出一个整数表示答案 17 | 18 | 备注: 19 | 1<=N<=pow(10, 13) 20 | */ 21 | 22 | /// 23 | /// 解题算法分析请参考 剑指offer/NumberOf1Between1AndN.cs 24 | /// 25 | using System; 26 | namespace NumberOf1From1ToN { 27 | class Solution { 28 | 29 | /// 30 | /// 注意这里需要使用long类型,因为1<=N<=pow(10, 13),超出了int类型的范围 31 | /// 32 | 33 | public long NumberOf1From1ToN(long n){ 34 | long count = 0; 35 | for(long i = 1; i <= n; i = i *10){ 36 | count += n / (i * 10) * i + Math.Min(Math.Max(0, n % (i * 10) - i + 1), i); 37 | } 38 | return count; 39 | } 40 | 41 | public static void Test() { 42 | string input = Console.ReadLine(); 43 | long n = long.Parse(input); 44 | Solution s = new Solution(); 45 | Console.WriteLine(s.NumberOf1From1ToN(n)); 46 | } 47 | 48 | } 49 | } -------------------------------------------------------------------------------- /程序员代码面试指南/PrintMinString.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 拼接所有的字符串产生字典序最小的字符串 4 | 5 | 题目描述: 6 | 给定一个字符串的数组strs,请找到一种拼接顺序, 7 | 使得所有的字符串拼接起来组成的字符串是所有可能性中字典序最小的,并返回这个字符串。 8 | 9 | 输入描述: 10 | 输入包含多行,第一行包含一个整数n( 1 <= n <= pow(10, 5) )代表字符串数组strs的长度 11 | 后面n行,每行一个字符串,代表strs[i](保证所有字符串长度都小于10)。 12 | 13 | 输出描述: 14 | 输出一行,包含一个字符串,代表返回的字典序最小的字符串。 15 | 16 | 备注: 17 | 时间复杂度O(nlog_2_n),额外空间复杂度O(1)。 18 | */ 19 | 20 | /// 21 | /// 解题算法分析请参考 剑指offer/PrintMinNumber.cs 22 | /// 23 | using System; 24 | namespace PrintMinString { 25 | 26 | class Solution { 27 | 28 | public int Compare(string a, string b){ 29 | return (a + b).CompareTo((b + a)); 30 | } 31 | public string PrintMinString(string[] strs){ 32 | Array.Sort(strs, Compare); 33 | return string.Join("", strs); 34 | } 35 | 36 | public static void Test() { 37 | string input = Console.ReadLine(); 38 | int n = int.Parse(input); 39 | string[] strs = new string[n]; 40 | for(int i = 0; i < n; i ++){ 41 | strs[i] = Console.ReadLine(); 42 | } 43 | Solution s = new Solution(); 44 | Console.WriteLine(s.PrintMinString(strs)); 45 | } 46 | 47 | } 48 | } -------------------------------------------------------------------------------- /Leetcode/SpiralOrder.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 螺旋矩阵 4 | 5 | 题目描述: 6 | 给定一个包含 m x n 个元素的矩阵(m 行, n 列),请按照顺时针螺旋顺序,返回矩阵中的所有元素。 7 | 8 | 示例 1: 9 | 输入: 10 | [ 11 | [ 1, 2, 3 ], 12 | [ 4, 5, 6 ], 13 | [ 7, 8, 9 ] 14 | ] 15 | 输出: [1,2,3,6,9,8,7,4,5] 16 | 17 | 示例 2: 18 | 输入: 19 | [ 20 | [1, 2, 3, 4], 21 | [5, 6, 7, 8], 22 | [9,10,11,12] 23 | ] 24 | 输出: [1,2,3,4,8,12,11,10,9,5,6,7] 25 | 26 | 代码结构: 27 | public class Solution { 28 | public IList SpiralOrder(int[][] matrix) { 29 | 30 | } 31 | } 32 | 33 | 题目链接: 34 | https://leetcode-cn.com/problems/spiral-matrix/ 35 | */ 36 | using System; 37 | using System.Collections.Generic; 38 | namespace SpiralOrder { 39 | 40 | class Solution { 41 | 42 | public IList SpiralOrder(int[][] matrix) { 43 | return new List(){1, 2}; 44 | } 45 | 46 | public void Print(IList list){ 47 | foreach (int item in list) 48 | { 49 | Console.Write(item + " "); 50 | } 51 | Console.WriteLine(); 52 | } 53 | 54 | public void Test() { 55 | int[][] matrix = new int[][]{ 56 | new int[]{1, 2, 3}, 57 | new int[]{4, 5, 6}, 58 | new int[]{7, 8, 9}, 59 | }; 60 | 61 | Print(SpiralOrder(matrix)); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /剑指offer/RectCover.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 矩形覆盖 4 | 5 | 题目描述: 6 | 我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形。 7 | 请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法? 8 | 9 | 比如n=3时,2*3的矩形块有3种覆盖方法 10 | 11 | 代码结构: 12 | class Solution 13 | { 14 | public int rectCover(int number) 15 | { 16 | // write code here 17 | } 18 | } 19 | */ 20 | using System; 21 | namespace RectCover { 22 | 23 | class Solution { 24 | 25 | /// 26 | /// 解法1 27 | /// 基本思路: 28 | /// 用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,假设有F(n)种方法 29 | /// 可以先用一个2*1的小矩形,竖着覆盖大矩形,则还剩下2*(n-1)的大矩形需要覆盖,即有F(n-1)种方法 30 | /// 也可以先用一个2*1的小矩形,横着覆盖大矩形。则还剩下2*(n-2)的大矩形需要覆盖,即有F(n-2)种方法 31 | /// 由以上两种情况可知,F(n) = F(n - 1) + F(n - 2),这不就是斐波那契数列嘛 32 | /// 所以求斐波那契数列的解法都可以用于这道题 详情可参考 Fibonacci.cs 文件 33 | /// 这里给出简单的递归解法,其余斐波那契数列解法不再赘述 34 | /// 图文介绍 https://www.cnblogs.com/iwiniwin/p/11006962.html 35 | /// 36 | 37 | public int RectCover(int number) 38 | { 39 | if(number <= 2) return number; 40 | return RectCover(number - 1) + RectCover(number - 2); 41 | } 42 | 43 | public void Test() { 44 | 45 | int number = 3; 46 | // number = 2; 47 | // number = 1; 48 | // number = 0; 49 | 50 | Console.WriteLine(RectCover(number)); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /剑指offer/ReplaceSpace.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 替换空格 4 | 5 | 题目描述: 6 | 请实现一个函数,将一个字符串中的每个空格替换成“%20”。 7 | 例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。 8 | 9 | 代码结构: 10 | class Solution 11 | { 12 | public string ReplaceSpace(string str) 13 | { 14 | // write code here 15 | } 16 | } 17 | */ 18 | using System; 19 | using System.Text; 20 | namespace ReplaceSpace { 21 | 22 | class Solution { 23 | 24 | /// 25 | /// 解法 26 | /// 基本思路: 27 | /// 这道题比较简单,就是遍历该字符串每一个字符进行复制,遇到空格就复制成%20 28 | /// 这里针对C#语言使用了StringBuilder,在频繁改动字符串时,相比直接使用string类型,减少了内存开销 29 | /// 30 | 31 | public string ReplaceSpace(string str) 32 | { 33 | StringBuilder builder = new StringBuilder(); 34 | for (int i = 0; i < str.Length ; i ++) 35 | { 36 | if(str[i] == ' '){ 37 | builder.Append("%20"); 38 | }else{ 39 | builder.Append(str[i]); 40 | } 41 | } 42 | return builder.ToString(); 43 | } 44 | 45 | public void Test() { 46 | 47 | string str = "We Are Happy"; 48 | str = " We Are Happy "; 49 | 50 | Console.WriteLine(ReplaceSpace(str)); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /剑指offer/FirstNotRepeatingChar.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 第一个只出现一次的字符 4 | 5 | 题目描述: 6 | 在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 7 | 如果没有则返回 -1(需要区分大小写). 8 | 9 | 代码结构: 10 | class Solution 11 | { 12 | public int FirstNotRepeatingChar(string str) 13 | { 14 | // write code here 15 | } 16 | } 17 | */ 18 | using System; 19 | namespace FirstNotRepeatingChar { 20 | class Solution { 21 | 22 | /// 23 | /// 解法 24 | /// 解题思路: 25 | /// 先遍历一遍字符串统计每个字符的出现次数,然后再遍历一遍字符串,找出第一个出现次数为1的字符的索引 26 | /// 利用0 - 57的数组下标,来对应A(65) - Z(90) - a(97) - z(122)的ASCII值 27 | /// 例如65对应的是下标0, 122对应的是下标57 28 | /// 29 | 30 | public int FirstNotRepeatingChar(string str) 31 | { 32 | if(str == null){ 33 | return - 1; 34 | } 35 | int[] arr = new int[58]; 36 | for(int i = 0; i < str.Length; i ++){ 37 | arr[(int)str[i] - 65] ++; 38 | } 39 | for(int i = 0; i < str.Length; i ++){ 40 | if(arr[(int)str[i] - 65] == 1){ 41 | return i; 42 | } 43 | } 44 | return -1; 45 | } 46 | 47 | public void Test() { 48 | string str = "abcdAddcfa"; 49 | // str = null; 50 | 51 | Console.WriteLine(FirstNotRepeatingChar(str)); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /剑指offer/Add.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 不用加减乘除做加法 4 | 5 | 题目描述: 6 | 写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。 7 | 8 | 代码结构: 9 | class Solution 10 | { 11 | public int Add(int num1, int num2) 12 | { 13 | // write code here 14 | } 15 | } 16 | */ 17 | using System; 18 | namespace Add { 19 | 20 | class Solution { 21 | 22 | /// 23 | /// 解法,位运算 24 | /// 基本思路: 25 | /// 以十进制加法为例,36 + 78 26 | /// 加法过程可以分为三步: 27 | /// 1. 不考虑进位,相加各位的值。得到 04 28 | /// 2. 计算进位(该位有进位则为1,无进位则为0)。得到 11。再左移一位。最终得到 110 29 | /// 3. 使用得到的两个值,重复上面两步。 04 + 110 得到 114 30 | /// 二进制同理也可以使用上面3步模拟加法 31 | /// 1. 不考虑进位,相加各位的值。这里不能使用加法,使用异或运算代替。00得0 01得1 11得0 32 | /// 2. 计算进位。这里利用与运算得到。00得0 01得0 11得1(进位) 33 | /// 负数是使用补码表示,所以同样适用上述算法 34 | /// 35 | 36 | public int Add(int num1, int num2) 37 | { 38 | while(num2 != 0){ 39 | int temp = num1 ^ num2; 40 | num2 &= num1; 41 | num2 <<= 1; 42 | num1 = temp; 43 | } 44 | return num1; 45 | } 46 | 47 | public void Test() { 48 | 49 | int num1 = 1; 50 | // num1 = 0; 51 | num1 = -6; 52 | 53 | int num2 = 4; 54 | num2 = -3; 55 | // num2 = 0; 56 | 57 | Console.WriteLine(Add(num1, num2)); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Leetcode/ContainsDuplicate.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 存在重复元素 4 | 5 | 题目描述: 6 | 给定一个整数数组,判断是否存在重复元素。 7 | 如果任意一值在数组中出现至少两次,函数返回 true 。如果数组中每个元素都不相同,则返回 false 。 8 | 9 | 示例 1: 10 | 输入: [1,2,3,1] 11 | 输出: true 12 | 13 | 示例 2: 14 | 输入: [1,2,3,4] 15 | 输出: false 16 | 17 | 示例 3: 18 | 输入: [1,1,1,3,3,4,3,2,4,2] 19 | 输出: true 20 | 21 | 代码结构: 22 | public class Solution { 23 | public bool ContainsDuplicate(int[] nums) { 24 | 25 | } 26 | } 27 | 28 | 题目链接: 29 | https://leetcode-cn.com/problems/contains-duplicate/ 30 | 31 | 补充: 32 | 本题目除了利用哈希表和朴素的线性查找(双层循环直接查找)外,还可以先对数组进行排序,如果有重复元素,排序后一定是相邻的 33 | */ 34 | using System; 35 | using System.Collections.Generic; 36 | namespace ContainsDuplicate { 37 | 38 | class Solution { 39 | 40 | /// 41 | /// 解法 42 | /// 基本思路: 43 | /// 利用哈希表 44 | /// 这里使用C#的HashSet,基于散列值,插入元素非常快,不包含重复项,插入重复元素时会返回false 45 | /// 46 | 47 | public bool ContainsDuplicate(int[] nums) { 48 | HashSet set = new HashSet(); 49 | for(int i = 0; i < nums.Length; i ++){ 50 | if(set.Add(nums[i]) == false) 51 | return true; 52 | } 53 | return false; 54 | } 55 | 56 | public void Test() { 57 | int[] nums = new int[]{1, 2, 3, 1}; 58 | // nums = new int[]{1, 2, 3, 4}; 59 | // nums = new int[]{1, 1, 1, 3, 3, 4, 3, 2, 4, 2}; 60 | 61 | Console.WriteLine(ContainsDuplicate(nums)); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /剑指offer/IsPopOrder.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 栈的压入、弹出序列 4 | 5 | 题目描述: 6 | 输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。 7 | 假设压入栈的所有数字均不相等。 8 | 例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列, 9 | 但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的) 10 | 11 | 代码结构: 12 | class Solution 13 | { 14 | public bool IsPopOrder(int[] pushV, int[] popV) 15 | { 16 | // write code here 17 | } 18 | } 19 | */ 20 | using System; 21 | using System.Collections.Generic; 22 | namespace IsPopOrder { 23 | 24 | class Solution { 25 | 26 | /// 27 | /// 解法 28 | /// 基本思路: 29 | /// 使用一个辅助栈根据pushV来模拟入栈,同时通过popV模拟出栈 30 | /// 当辅助栈最后为空,则表示popV是pushV的弹出序列 31 | /// 32 | 33 | public bool IsPopOrder(int[] pushV, int[] popV) 34 | { 35 | Stack stack = new Stack(); 36 | for(int i = 0, j = 0; i < pushV.Length; i ++){ 37 | stack.Push(pushV[i]); 38 | while(stack.Count > 0 && stack.Peek() == popV[j]){ 39 | stack.Pop(); 40 | j ++; 41 | } 42 | } 43 | return stack.Count == 0; 44 | } 45 | 46 | public void Test() { 47 | 48 | int[] pushV = new int[]{1, 2, 3, 4, 5}; 49 | // pushV = new int[]{1, 2}; 50 | 51 | int[] popV = new int[]{4, 5, 3, 2, 1}; 52 | // popV = new int[]{4, 3, 5, 1, 2}; 53 | // popV = new int[]{1, 2}; 54 | 55 | Console.WriteLine(IsPopOrder(pushV, popV)); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /排序算法/SimpleInsertionSort.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 算法名称: 3 | 简单插入排序 4 | 5 | 时间复杂度:O(n^2) 6 | 7 | 空间复杂度:O(1) 8 | 9 | 最好情况: 10 | O(n) 正序有序,要插入的元素总是插入在末尾 11 | 12 | 最坏情况: 13 | O(n^2) 逆序有序 14 | 15 | 稳定性:稳定 16 | 17 | 优点:稳定,快,如果序列是基本有序的,使用直接插入排序效率就非常高 18 | 19 | 缺点:比较次数不一定,比较次数越多,插入点后移的数据越多,特别是数据量庞大的时候。不过使用链表可以解决这个问题 20 | */ 21 | using System; 22 | namespace SimpleInsertionSort { 23 | 24 | class Solution { 25 | 26 | /// 27 | /// 基本思想: 28 | /// 每次将一个待排序列的元素,按其大小插入到前面已经排好序序列中的合适位置,直到全部元素插完为止 29 | /// 自然地,插入的时候,后面的元素要依次往后移 30 | /// 这里默认待排序列的第一个元素是排好序的 31 | /// 32 | 33 | public void SimpleInsertionSort(int[] array){ 34 | for(int i = 1; i < array.Length; i ++){ 35 | int j = i; 36 | int target = array[j]; 37 | while(j > 0 && target < array[j - 1]){ 38 | array[j] = array[j - 1]; 39 | j --; 40 | } 41 | array[j] = target; 42 | } 43 | } 44 | 45 | public void Test() { 46 | 47 | int[] array = new int[]{4, 1, 2, 3}; 48 | // array = new int[]{1, 1, 1, 1, 0, 0}; 49 | // array = new int[]{1}; 50 | 51 | SimpleInsertionSort(array); 52 | 53 | Print(array); 54 | } 55 | 56 | public void Print(int[] array){ 57 | foreach (int item in array) 58 | { 59 | Console.Write(item + " "); 60 | } 61 | Console.WriteLine(); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /剑指offer/Multiply.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 构建乘积数组 4 | 5 | 题目描述: 6 | 给定一个数组A[0,1,...,n-1],请构建一个数组B[0,1,...,n-1],其中B中的元素B[i]=A[0]*A[1]*...*A[i-1]*A[i+1]*...*A[n-1]。不能使用除法。 7 | 8 | 代码结构: 9 | class Solution 10 | { 11 | public int[] multiply(int[] A) 12 | { 13 | // write code here 14 | } 15 | } 16 | */ 17 | using System; 18 | namespace Multiply { 19 | 20 | class Solution { 21 | 22 | /// 23 | /// 解法1 24 | /// 基本思路: 25 | /// 对于每个元素B[i] 26 | /// 先计算i左边的元素乘积 B[i] = B[0] * B[1] * B[2] * ... * B[i - 1] 27 | /// 再计算i右边的元素乘积 B[i] = B[i + 1] * B[i + 2] * ... * B[n - 1] 28 | /// 再把两边的乘积相乘 29 | /// 30 | 31 | public int[] Multiply(int[] A) 32 | { 33 | if (A == null) return A; 34 | int[] B = new int[A.Length]; 35 | int ret = 1; 36 | for(int i = 0; i < A.Length; ret *= A[i ++]){ 37 | B[i] = ret; 38 | } 39 | ret = 1; 40 | for(int i = A.Length - 1; i >= 0; ret *= A[i --]) { 41 | B[i] *= ret; 42 | } 43 | return B; 44 | } 45 | 46 | public void Print(int[] array) { 47 | foreach (int item in array) 48 | { 49 | Console.WriteLine(item); 50 | } 51 | } 52 | 53 | public void Test() { 54 | 55 | int[] A = new int[]{1, 2, 3, 4}; 56 | // A = new int[]{1, 2, 3, 4, 5}; 57 | // A = new int[]{0}; 58 | // A = new int[]{0, 1}; 59 | 60 | Print(Multiply(A)); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /剑指offer/Find.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 二维数组中的查找 4 | 5 | 题目描述: 6 | 在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序, 7 | 每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。 8 | 9 | 代码结构: 10 | class Solution 11 | { 12 | public bool Find(int target, int[][] array) 13 | { 14 | // write code here 15 | } 16 | } 17 | 18 | 想法: 19 | 先从左到右遍历,如果元素没有target大,就往右找,如果比target小,就不知道应该怎么找了 20 | 如果下面的元素都比target小,不就可以向下找了吗 21 | 对,可以从左下角开始找,从左到右递增,从下到上递减 22 | 1 2 3 4 23 | 2 3 4 5 24 | */ 25 | using System; 26 | namespace Find { 27 | 28 | class Solution { 29 | 30 | /// 31 | /// 解法1 32 | /// 基本思路: 33 | /// 根据数组从左到右递增,从上到下递增的特性可以发现, 34 | /// 当从数组的左下角开始判断时,从左到右是递增的,从下到上是递减的 35 | /// 此时,如果target比当前元素大,就向右找,如果比当前元素小,就向上找 36 | /// 从数组的右上角开始也是类似的 37 | /// 38 | 39 | public bool Find(int target, int[][] array) 40 | { 41 | int row = array.Length - 1, col = 0; 42 | while(row >= 0 && col < array[row].Length){ 43 | if(array[row][col] == target){ 44 | return true; 45 | }else if(array[row][col] > target){ 46 | row --; 47 | }else{ 48 | col ++; 49 | } 50 | } 51 | return false; 52 | } 53 | 54 | public void Test() { 55 | 56 | int target = 8; 57 | int[][] array = new int[][]{ 58 | new int[]{1, 2, 3, 6}, 59 | new int[]{2, 3, 4, 10} 60 | }; 61 | 62 | Console.WriteLine(Find(target, array)); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /剑指offer/FindKthToTail.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 链表中倒数第k个结点 4 | 5 | 题目描述: 6 | 输入一个链表,输出该链表中倒数第k个结点。 7 | 8 | 代码结构: 9 | class Solution 10 | { 11 | public ListNode FindKthToTail(ListNode head, int k) 12 | { 13 | // write code here 14 | } 15 | } 16 | */ 17 | using System; 18 | namespace FindKthToTail { 19 | 20 | public class ListNode 21 | { 22 | public int val; 23 | public ListNode next; 24 | public ListNode (int x) 25 | { 26 | val = x; 27 | } 28 | } 29 | 30 | class Solution { 31 | 32 | /// 33 | /// 解法 34 | /// 基本思路: 35 | /// 使用两个指针p, q,通过p = p.next不断遍历链表,当p先走了k步后,q指针再通过q = q.next往下走 36 | /// 这样相当于p指针一直提前q指针k个节点 37 | /// 当p指针指向链表末尾时,q指针指向的就是倒数第k个结点 38 | /// 39 | 40 | public ListNode FindKthToTail(ListNode head, int k) 41 | { 42 | ListNode p = head, q = null; 43 | while(p != null){ 44 | if(q != null) 45 | q = q.next; 46 | else if(--k == 0) 47 | q = head; 48 | p = p.next; 49 | } 50 | return q; 51 | } 52 | 53 | public void Test() { 54 | 55 | ListNode head = new ListNode(1); 56 | head.next = new ListNode(2); 57 | head.next.next = new ListNode(3); 58 | head.next.next.next = new ListNode(4); 59 | 60 | int k = 3; 61 | // k = 1; 62 | // k = 0; 63 | // k = -3; 64 | 65 | ListNode node = FindKthToTail(head, k); 66 | if(node == null) 67 | Console.WriteLine("null"); 68 | else 69 | Console.WriteLine(node.val); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Leetcode/AddStrings.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 字符串相加 4 | 5 | 题目描述: 6 | 给定两个字符串形式的非负整数 num1 和num2 ,计算它们的和。 7 | 8 | 注意: 9 | 1. num1 和 num2 的长度都小于 5100. 10 | 2. num1 和 num2 都只包含数字 0-9. 11 | 3. num1 和 num2 都不包含任何前导零。 12 | 4. 你不能使用任何內建 BigInteger 库, 也不能直接将输入的字符串转换为整数形式。 13 | 14 | 代码结构: 15 | public class Solution { 16 | public string AddStrings(string num1, string num2) { 17 | 18 | } 19 | } 20 | 21 | 题目链接: 22 | https://leetcode-cn.com/problems/add-strings/ 23 | */ 24 | using System; 25 | using System.Collections.Generic; 26 | namespace AddStrings { 27 | 28 | class Solution { 29 | 30 | /// 31 | /// 解法,模拟竖式加法 32 | /// 基本思路: 33 | /// 使用i, j两个指针指向两个字符串的末尾 34 | /// 从字符串末尾开始向前依次相加i,j所指向的元素 35 | /// 将两数之和对10求余,获取该位上的数值 36 | /// 将两数之和除以10,获取是否有进位 37 | /// 当较短的字符串先走完时,均以0代替 38 | /// 39 | 40 | public string AddStrings(string num1, string num2) { 41 | string res = ""; 42 | int i = num1.Length - 1, j = num2.Length - 1; 43 | int carry = 0; 44 | while(i >= 0 || j >= 0){ 45 | int n1 = i >= 0 ? num1[i] - '0' : 0; 46 | int n2 = j >= 0 ? num2[j] - '0' : 0; 47 | int sum = n1 + n2 + carry; 48 | res = (sum % 10) + res; 49 | carry = sum / 10; 50 | i --; 51 | j --; 52 | } 53 | if(carry == 1) res = 1 + res; 54 | return res; 55 | } 56 | 57 | public void Test() { 58 | string num1 = "129"; 59 | // num1 = "0"; 60 | // num1 = "1"; 61 | 62 | string num2 = "459"; 63 | // num2 = "9"; 64 | 65 | Console.WriteLine(AddStrings(num1, num2)); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /剑指offer/DeleteNode.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 删除链表的节点 4 | 5 | 题目描述: 6 | 给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。返回删除后的链表的头节点。 7 | 8 | 1.此题对比原题有改动 9 | 2.题目保证链表中节点的值互不相同 10 | 3.该题只会输出返回的链表和结果做对比,所以若使用 C 或 C++ 语言,你不需要 free 或 delete 被删除的节点 11 | 12 | 代码结构: 13 | class Solution { 14 | public ListNode deleteNode (ListNode head, int val) { 15 | // write code here 16 | } 17 | } 18 | */ 19 | using System; 20 | using System.Collections.Generic; 21 | namespace DeleteNode { 22 | public class ListNode 23 | { 24 | public int val; 25 | public ListNode next; 26 | 27 | public ListNode (int x) 28 | { 29 | val = x; 30 | } 31 | } 32 | 33 | class Solution { 34 | public ListNode DeleteNode (ListNode head, int val) { 35 | if(head == null) return null; 36 | if(head.val == val) return head.next; 37 | ListNode node = head; 38 | while(node.next != null) { 39 | if(node.next.val == val) { 40 | node.next = node.next.next; 41 | break; 42 | } 43 | node = node.next; 44 | } 45 | return head; 46 | } 47 | 48 | public void Test() 49 | { 50 | ListNode head = new ListNode(2); 51 | head.next = new ListNode(5); 52 | head.next.next = new ListNode(1); 53 | head.next.next.next = new ListNode(9); 54 | int val = 5; 55 | 56 | ListNode result = DeleteNode(head, val); 57 | while (head != null) { 58 | Console.Write(head.val + " "); 59 | head = head.next; 60 | } 61 | Console.WriteLine(); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /剑指offer/SimulateQueueWithStack.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 用两个栈实现队列 4 | 5 | 题目描述: 6 | 用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。 7 | 8 | 代码结构: 9 | class Solution 10 | { 11 | public void push(int node) 12 | { 13 | 14 | } 15 | public int pop() 16 | { 17 | 18 | } 19 | } 20 | */ 21 | using System; 22 | using System.Collections.Generic; 23 | namespace SimulateQueueWithStack { 24 | 25 | class Solution { 26 | 27 | /// 28 | /// 解法 29 | /// 基本思路: 30 | /// 队列的特性是先进先出,而栈的特性是先进后出,可以发现他们的顺序刚好是相反的。 31 | /// 那么自然就想到相反的相反的就是对的顺序了 32 | /// 举个例子,仍然是往栈A中依次插入1,2,3,4,此时它的弹出顺序是4,3,2,1。 33 | /// 若再将这个弹出顺序4,3,2,1,依次插入到栈B中,此时栈B的弹出顺序就是1,2,3,4, 34 | /// 对于最开始插入的1,2,3,4序列刚好满足了先进先出的特性。 35 | /// 栈和队列的介绍 https://www.cnblogs.com/iwiniwin/p/10793651.html 36 | /// 37 | 38 | Stack pushStack = new Stack(); 39 | Stack popStack = new Stack(); 40 | 41 | public void Push(int node) 42 | { 43 | pushStack.Push(node); 44 | } 45 | 46 | public int Pop() 47 | { 48 | if(popStack.Count == 0){ 49 | while(pushStack.Count > 0){ 50 | popStack.Push(pushStack.Pop()); 51 | } 52 | } 53 | return popStack.Pop(); 54 | } 55 | 56 | public void Test() { 57 | 58 | Push(1); 59 | Push(2); 60 | Console.WriteLine(Pop()); 61 | Push(3); 62 | Console.WriteLine(Pop()); 63 | Push(4); 64 | Push(5); 65 | Console.WriteLine(Pop()); 66 | Console.WriteLine(Pop()); 67 | Console.WriteLine(Pop()); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /剑指offer/PrintMinNumber.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 把数组排成最小的数 4 | 5 | 题目描述: 6 | 输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。 7 | 例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。 8 | 9 | 代码结构: 10 | class Solution 11 | { 12 | public string PrintMinNumber(int[] numbers) 13 | { 14 | // write code here 15 | } 16 | } 17 | */ 18 | using System; 19 | namespace PrintMinNumber { 20 | class Solution { 21 | 22 | /// 23 | /// 解法 24 | /// 基本思路: 25 | /// 将数组中的元素按从小到大的顺序排序,得到的就是最小数字。 26 | /// 使用任何的排序算法都可以,这里使用的是冒泡排序 27 | /// 重点是如何判断两个元素(不同元素的长度可能不同)的大小。 28 | /// 这里通过(a + b)和(b + a)判断,如果前者小于后者,则说明a < b,反之亦然。 29 | /// 30 | public bool Less(string a, string b){ 31 | return (a + b).CompareTo(b + a) < 0; 32 | } 33 | public void Swap(int[] numbers, int i, int j){ 34 | int temp = numbers[i]; 35 | numbers[i] = numbers[j]; 36 | numbers[j] = temp; 37 | } 38 | public string PrintMinNumber(int[] numbers) 39 | { 40 | if (numbers == null){ 41 | return ""; 42 | } 43 | for(int i = 0; i < numbers.Length; i ++){ 44 | for(int j = numbers.Length - 1; j > i; j --){ 45 | if(Less(numbers[j].ToString(), numbers[j - 1].ToString())){ 46 | Swap(numbers, j, j - 1); 47 | } 48 | } 49 | } 50 | return string.Join("", numbers); 51 | } 52 | 53 | public void Test() { 54 | int[] numbers = new int[]{3, 32, 321}; 55 | // numbers = new int[]{}; 56 | // numbers = null; 57 | 58 | Console.WriteLine(PrintMinNumber(numbers)); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /排序算法/CountSort.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 算法名称: 3 | 计数排序 4 | 5 | 时间复杂度:O(n + k),k常数,即待排序的最大值 6 | 7 | 空间复杂度:O(k) 8 | 9 | 最好情况: 10 | O(n + k) 11 | 12 | 最坏情况: 13 | O(n + k) 14 | 15 | 稳定性:稳定 16 | 17 | 优点:稳定,适用于最大值不是很大的整数序列,在k值较小时突破了基于比较的排序的算法下限 18 | 19 | 缺点:存在前提条件,k值较大时,需要大量额外空间 20 | */ 21 | using System; 22 | namespace CountSort { 23 | 24 | class Solution { 25 | 26 | /// 27 | /// 基本思想: 28 | /// 采用空间换时间的方法。 29 | /// 得到待排序列中的最大值,构建 最大值 + 1 的计数数组 30 | /// 遍历待排序列,在计数数组中统计每个元素出现的次数。出现一个元素,则以该元素值为索引的位置上计数加1 31 | /// 然后遍历计数数组,若对应索引上的值大于0,则表示存在有值为对应索引的元素,依次将存在的元素赋值到原始序列中 32 | /// 33 | 34 | public void CountSort(int[] array){ 35 | int max = array[0]; 36 | for(int i = 0; i < array.Length; i ++){ 37 | if(array[i] > max) max = array[i]; 38 | } 39 | int[] count = new int[max + 1]; 40 | for(int i = 0; i < array.Length; i ++){ 41 | count[array[i]] ++; 42 | } 43 | int index = 0; 44 | for(int i = 0; i < count.Length; i ++){ 45 | while(count[i] -- > 0){ 46 | array[index ++] = i; 47 | } 48 | } 49 | } 50 | 51 | public void Test() { 52 | 53 | int[] array = new int[]{1, 4, 2, 3, 0}; 54 | 55 | array = new int[]{1, 0, 1, 2, 0}; 56 | 57 | array = new int[]{100, 3, 0, 1, 1, 4, 99, 30, 100}; 58 | 59 | CountSort(array); 60 | 61 | Print(array); 62 | } 63 | 64 | public void Print(int[] array){ 65 | foreach (int item in array) 66 | { 67 | Console.Write(item + " "); 68 | } 69 | Console.WriteLine(); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /LeetcodeProgram.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Algorithm 4 | { 5 | /// 6 | /// Leetcode算法题目 模块 7 | /// 8 | class LeetcodeProgram 9 | { 10 | public static void Test() 11 | { 12 | 13 | // new TwoSum.Solution().Test(); // 两数之和 14 | // new FindMedianSortedArrays.Solution().Test(); // 寻找两个正序数组的中位数 15 | // new LongestPalindrome.Solution().Test(); // 最长回文子串 16 | // new Atoi.Solution().Test(); // 字符串转换整数 (atoi) 17 | // new LongestCommonPrefix.Solution().Test(); // 最长公共前缀 18 | // new ThreeSum.Solution().Test(); // 三数之和 19 | // new ThreeSumClosest.Solution().Test(); // 最接近的三数之和 20 | // new IsValidParentheses.Solution().Test(); // 有效的括号 21 | // new RemoveDuplicates.Solution().Test(); // 删除排序数组中的重复项 22 | // new MaxArea.Solution().Test(); // 盛最多水的容器 23 | // new StringMultiply.Solution().Test(); // 字符串相乘 24 | // new ReverseString.Solution().Test(); // 反转字符串 25 | // new ReverseWords.Solution().Test(); // 反转字符串中的单词 III 26 | // new ProductExceptSelf.Solution().Test(); // 除自身以外数组的乘积 27 | // new ContainsDuplicate.Solution().Test(); // 存在重复元素 28 | // new SpiralOrder.Solution().Test(); // 螺旋矩阵 29 | // new ConstructQuadTree.Solution().Test(); // 建立四叉树 30 | 31 | 32 | // new AddStrings.Solution().Test(); // 字符串相加 33 | new RectangleOverlap.Solution().Test(); // 矩形重叠 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /剑指offer/StackWithMin.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 包含min函数的栈 4 | 5 | 题目描述: 6 | 定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))。 7 | 注意:保证测试中不会当栈为空的时候,对栈调用pop()或者min()或者top()方法。 8 | 9 | 代码结构: 10 | class Solution 11 | { 12 | public void push(int node) 13 | { 14 | 15 | } 16 | public void pop() 17 | { 18 | 19 | } 20 | public int top() 21 | { 22 | 23 | } 24 | public int min() 25 | { 26 | 27 | } 28 | } 29 | */ 30 | using System; 31 | using System.Collections.Generic; 32 | namespace StackWithMin { 33 | 34 | class Solution { 35 | 36 | /// 37 | /// 解法 38 | /// 基本思路: 39 | /// 使用一个辅助栈来保存最小元素 40 | /// 每次入栈时,如果元素小于等于辅助栈栈顶元素,就入辅助栈 41 | /// 每次出栈时,如果出栈元素也是辅助栈栈顶元素,则辅助栈也出栈 42 | /// 这样可以保证辅助栈的栈顶元素一直是最小元素 43 | /// 44 | 45 | Stack stack = new Stack(); 46 | Stack minStack = new Stack(); 47 | 48 | public void push(int node) 49 | { 50 | stack.Push(node); 51 | if(minStack.Count == 0) 52 | minStack.Push(node); 53 | else if(node <= minStack.Peek()) 54 | minStack.Push(node); 55 | } 56 | 57 | public void pop() 58 | { 59 | int node = stack.Pop(); 60 | if(node == minStack.Peek()) 61 | minStack.Pop(); 62 | } 63 | 64 | public int top() 65 | { 66 | return stack.Peek(); 67 | } 68 | 69 | public int min() 70 | { 71 | return minStack.Peek(); 72 | } 73 | 74 | public void Test() { 75 | 76 | push(3); 77 | push(2); 78 | pop(); 79 | push(1); 80 | push(1); 81 | pop(); 82 | 83 | Console.WriteLine(min()); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /排序算法/ShellSort.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 算法名称: 3 | 希尔排序 4 | 5 | 时间复杂度:O(n*log(2, n)) 6 | 7 | 空间复杂度:O(1) 8 | 9 | 最好情况: 10 | O(n*log(2, n)) 因为希尔排序的执行时间依赖于增量d,如何选择增量使得希尔排序的元素比较次数和移动次数较少 11 | 这个问题目前还未能解决 12 | 13 | 最坏情况: 14 | O(n*log(2, n)) 15 | 16 | 稳定性:不稳定 相同元素可能分在不同的组内,导致顺序可能发生改变 17 | 18 | 优点:快,数据移动少 19 | 20 | 缺点:不稳定,d的取值是多少,应该取多少个不同的值,都无法确切知道,只能凭经验来取 21 | */ 22 | using System; 23 | namespace ShellSort { 24 | 25 | class Solution { 26 | 27 | /// 28 | /// 基本思想: 29 | /// 希尔排序是直接插入排序算法的优化,又称为缩小增量式插入排序 30 | /// 对于待排数组,取一个增量d,根据增量d作为下标间隔,将待排数组分割成若干个组 31 | /// 各组内部做直接插入排序,然后不断减小增量d,重复上述过程 32 | /// 直到增量d为1时,即所有元素在同一组内进行直接插入排序,完成最终的排序 33 | /// 34 | 35 | public void ShellSort(int[] array){ 36 | int d = array.Length / 2; 37 | while(d >= 1){ 38 | for(int i = d; i < array.Length; i ++){ 39 | int j = i; 40 | int target = array[j]; 41 | while(j >= d && target < array[j -d]){ 42 | array[j] = array[j - d]; 43 | j -= d; 44 | } 45 | array[j] = target; 46 | } 47 | d /= 2; 48 | } 49 | } 50 | 51 | public void Test() { 52 | 53 | int[] array = new int[]{1, 3, 2, 4}; 54 | // array = new int[]{0, 1, 0, 0, 0, -1, 1}; 55 | array = new int[]{4, 4, 5, 1, 1, 1, -2, -2, -2, 3, 3, 3}; 56 | 57 | ShellSort(array); 58 | 59 | Print(array); 60 | } 61 | 62 | public void Print(int[] array){ 63 | foreach (int item in array) 64 | { 65 | Console.Write(item + " "); 66 | } 67 | Console.WriteLine(); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /剑指offer/HasSubtree.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 树的子结构 4 | 5 | 题目描述: 6 | 输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构) 7 | 8 | 代码结构: 9 | class Solution 10 | { 11 | public bool HasSubtree(TreeNode pRoot1, TreeNode pRoot2) 12 | { 13 | // write code here 14 | } 15 | } 16 | */ 17 | using System; 18 | namespace HasSubtree { 19 | 20 | public class TreeNode 21 | { 22 | public int val; 23 | public TreeNode left; 24 | public TreeNode right; 25 | public TreeNode (int x) 26 | { 27 | val = x; 28 | } 29 | } 30 | 31 | class Solution { 32 | 33 | /// 34 | /// 解法,递归 35 | /// 基本思路: 36 | /// 如果两颗树的根节点相同,则接着判断两颗树的左右子树是否是子树关系 37 | /// 如果也是的话直接返回true 38 | /// 如果不是的话,就什么也不做,因为目前仍不能判断B是不是A的子树 39 | /// 接着判断B是否是A的左子树的子树,如果是的直接返回true 40 | /// 接着判断B是否是A的右子树的子树,如果是的直接返回true 41 | /// 42 | 43 | public bool HasSubtree(TreeNode pRoot1, TreeNode pRoot2) 44 | { 45 | if(pRoot1 == null || pRoot2 == null) return false; 46 | if(pRoot1.val == pRoot2.val) 47 | if((pRoot2.left == null || HasSubtree(pRoot1.left, pRoot2.left)) && (pRoot2.right == null || HasSubtree(pRoot1.right, pRoot2.right))) 48 | return true; 49 | return HasSubtree(pRoot1.left, pRoot2) || HasSubtree(pRoot1.right, pRoot2); 50 | } 51 | 52 | public void Test() { 53 | 54 | TreeNode pRoot1 = new TreeNode(1); 55 | // pRoot1.left = new TreeNode(2); 56 | pRoot1.right = new TreeNode(1); 57 | pRoot1.right.left = new TreeNode(1); 58 | pRoot1.right.left.left = new TreeNode(2); 59 | 60 | TreeNode pRoot2 = new TreeNode(1); 61 | pRoot2.left = new TreeNode(2); 62 | 63 | Console.WriteLine(HasSubtree(pRoot1, pRoot2)); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Leetcode/ProductExceptSelf.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 除自身以外数组的乘积 4 | 5 | 题目描述: 6 | 给你一个长度为 n 的整数数组 nums,其中 n > 1,返回输出数组 output ,其中 output[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。 7 | 8 | 示例 1: 9 | 输入: [1,2,3,4] 10 | 输出: [24,12,8,6] 11 | 12 | 提示: 13 | 题目数据保证数组之中任意元素的全部前缀元素和后缀(甚至是整个数组)的乘积都在 32 位整数范围内。 14 | 15 | 说明: 16 | 请不要使用除法,且在 O(n) 时间复杂度内完成此题。 17 | 18 | 进阶: 19 | 你可以在常数空间复杂度内完成这个题目吗?( 出于对空间复杂度分析的目的,输出数组不被视为额外空间。) 20 | 21 | 代码结构: 22 | public class Solution { 23 | public int[] ProductExceptSelf(int[] nums) { 24 | 25 | } 26 | } 27 | 28 | 题目链接: 29 | https://leetcode-cn.com/problems/product-of-array-except-self/ 30 | */ 31 | using System; 32 | using System.Collections.Generic; 33 | namespace ProductExceptSelf { 34 | 35 | class Solution { 36 | 37 | /// 38 | /// 解法 39 | /// 基本思路: 40 | /// 对于每个元素nums[i] 41 | /// 先计算i左边的元素乘积 nums[i] = nums[0] * nums[1] * nums[2] * ... * nums[i - 1] 42 | /// 再计算i右边的元素乘积 nums[i] = nums[i + 1] * nums[i + 2] * ... * nums[n - 1] 43 | /// 再把两边的乘积相乘 44 | /// 45 | 46 | public int[] ProductExceptSelf(int[] nums) { 47 | int len = nums.Length; 48 | int[] res = new int[len]; 49 | int ret = 1; 50 | for(int i = 0; i < len; ret *= nums[i ++]) 51 | res[i] = ret; 52 | ret = 1; 53 | for(int i = len - 1; i >= 0; ret *= nums[i --]) 54 | res[i] *= ret; 55 | return res; 56 | } 57 | 58 | public void Print(int[] nums) { 59 | foreach (int item in nums) 60 | { 61 | Console.Write(item + " "); 62 | } 63 | Console.WriteLine(); 64 | } 65 | 66 | public void Test() { 67 | int[] nums = new int[]{1, 2, 3, 4}; 68 | // nums = new int[]{2, 3}; 69 | 70 | Print(ProductExceptSelf(nums)); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /排序算法/SimpleSelectionSort.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 算法名称: 3 | 简单选择排序 4 | 5 | 时间复杂度:O(n^2) 6 | 7 | 空间复杂度:O(1) 8 | 9 | 最好情况: 10 | O(n^2),此时不发生交换,但仍需进行比较 11 | 12 | 最坏情况: 13 | O(n^2) 14 | 15 | 稳定性:不稳定,因为在将最小或最大元素替换到前面时,可能将排在前面的相等元素交换到后面去 16 | 17 | 优点:交换数据的次数已知(n - 1)次 18 | 19 | 缺点:不稳定,比较的次数多 20 | */ 21 | using System; 22 | namespace SimpleSelectionSort { 23 | 24 | class Solution { 25 | 26 | /// 27 | /// 基本思想: 28 | /// 每一趟在待排序列中选出最小(或最大)的元素,依次放在已排好序的元素序列后面(或前面),直至全部的元素排完为止。 29 | /// 首先在待排序列中选出最小的元素,将它与第一个位置上的元素交换。 30 | /// 然后选出次小的元素,将它与第二个位置上的元素交换。以此类推,直至所有元素排成有序序列为止。 31 | /// 选择排序是对整体的选择。只有在确定了最小数(或最大数)的前提下才进行交换, 大大减少了交换的次数。 32 | /// 33 | 34 | public void SimpleSelectionSort(int[] array){ 35 | for(int i = 0; i < array.Length - 1; i ++){ 36 | int index = i; 37 | for(int j = index + 1; j < array.Length; j ++){ 38 | if(array[j] < array[index]){ 39 | index = j; 40 | } 41 | } 42 | if(index != i) 43 | Swap(array, i, index); 44 | } 45 | } 46 | 47 | public void Swap(int[] array, int i, int j){ 48 | int temp = array[i]; 49 | array[i] = array[j]; 50 | array[j] = temp; 51 | } 52 | 53 | public void Test() { 54 | 55 | int[] array = new int[]{1, 4, 2, 3}; 56 | // array = new int[]{1, 1, 1, 1, 0, 0, -1, -1}; 57 | // array = new int[]{4, 4, 5, 1, 1, 1, -2, -2, -2, 3, 3, 3}; 58 | 59 | SimpleSelectionSort(array); 60 | 61 | Print(array); 62 | } 63 | 64 | public void Print(int[] array){ 65 | foreach (int item in array) 66 | { 67 | Console.Write(item + " "); 68 | } 69 | Console.WriteLine(); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /剑指offer/GetUglyNumber.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 丑数 4 | 5 | 题目描述: 6 | 把只包含质因子2、3和5的数称作丑数(Ugly Number)。 7 | 例如6、8都是丑数,但14不是,因为它包含质因子7。 8 | 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。 9 | 10 | 代码结构: 11 | class Solution 12 | { 13 | public int GetUglyNumber_Solution(int index) 14 | { 15 | // write code here 16 | } 17 | } 18 | */ 19 | using System; 20 | using System.Collections.Generic; 21 | namespace GetUglyNumber { 22 | class Solution { 23 | 24 | /// 25 | /// 解法 26 | /// 基本思路: 27 | /// 丑数只包含因子2,3,5(除1以外),所以除1外的所有丑数都可以用2x * 3y * 5z表示 28 | /// 问题的关键在于如何按照从小到大的顺序获取丑数 29 | /// 显然的是每获取到一个丑数x,我们都可以通过2x,3x, 5x算出三个新的丑数,但是新算出的丑数和之前已经算出的丑数的大小不好比较 30 | /// 我们可以分三个队列来解决这个问题,分别是乘2,乘3,乘5的队列 31 | /// 通过乘以2得到的丑数都放到乘2的队列中,乘3和乘5的同理,这样单看每个队列中的丑数一定是从小到大排列的 32 | /// 此时这个三个队列头中的最小值就是整个待排丑数中的最小值,也就是我们要获取的下一个丑数值 33 | /// 下面的代码是利用q2,q3,q5一直表示各自队列的头被计算出来时使用的丑数的索引 34 | /// 35 | 36 | public int GetUglyNumber_Solution(int index) 37 | { 38 | if(index <= 0){ 39 | return 0; 40 | } 41 | List list = new List(){1}; 42 | int q2 = 0, q3 = 0, q5 = 0; 43 | for(int i = 1; i < index; i ++){ 44 | int num = Math.Min(list[q2] * 2, Math.Min(list[q3] * 3, list[q5] * 5)); 45 | if(list[q2] * 2 == num){ 46 | q2 ++; 47 | } 48 | if(list[q3] * 3 == num){ 49 | q3 ++; 50 | } 51 | if(list[q5] * 5 == num){ 52 | q5 ++; 53 | } 54 | list.Add(num); 55 | } 56 | return list[index - 1]; 57 | } 58 | 59 | public void Test() { 60 | int index = 7; 61 | // index = 11; 62 | 63 | Console.WriteLine(GetUglyNumber_Solution(index)); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Leetcode/IsValidParentheses.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 有效的括号 4 | 5 | 题目描述: 6 | 给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。 7 | 有效字符串需满足: 8 | 左括号必须用相同类型的右括号闭合。 9 | 左括号必须以正确的顺序闭合。 10 | 注意空字符串可被认为是有效字符串。 11 | 12 | 示例: 13 | 输入: "()" 14 | 输出: true 15 | 16 | 示例: 17 | 输入: "()[]{}" 18 | 输出: true 19 | 20 | 示例: 21 | 输入: "(]" 22 | 输出: false 23 | 24 | 示例: 25 | 输入: "([)]" 26 | 输出: false 27 | 28 | 示例: 29 | 输入: "{[]}" 30 | 输出: true 31 | 32 | 代码结构: 33 | public class Solution { 34 | public bool IsValid(string s) { 35 | 36 | } 37 | } 38 | 39 | 题目链接: 40 | https://leetcode-cn.com/problems/valid-parentheses/ 41 | 42 | 官方题解: 43 | https://leetcode-cn.com/problems/valid-parentheses/solution/you-xiao-de-gua-hao-by-leetcode/ 44 | */ 45 | using System; 46 | using System.Collections.Generic; 47 | namespace IsValidParentheses { 48 | 49 | class Solution { 50 | 51 | /// 52 | /// 解法 53 | /// 基本思路: 54 | /// 枚举字符串每个字符,利用栈 55 | /// 如果是闭括号,则判断是否是栈顶元素对应的闭括号,如果是则弹出栈顶元素(找到了一对子有效括号)。 56 | /// 如果是其它括号,就入栈 57 | /// 最后如果栈中元素为0,则表示是有效括号,因为它的子串都是子有效括号,都被弹出了 58 | /// 59 | 60 | public bool IsValid(string s) { 61 | Stack stack = new Stack(); 62 | for(int i = 0; i < s.Length; i ++){ 63 | if(stack.Count == 0){ 64 | stack.Push(s[i]); 65 | continue; 66 | } 67 | char top = stack.Peek(); 68 | if((top == '(' && s[i] == ')') || (top == '[' && s[i] == ']') || (top == '{' && s[i] == '}')) 69 | stack.Pop(); 70 | else 71 | stack.Push(s[i]); 72 | } 73 | return stack.Count == 0; 74 | } 75 | 76 | public void Test() { 77 | string s = ""; 78 | s = "()"; 79 | s = "()[]{}"; 80 | s = "(]"; 81 | s = "([)]"; 82 | s = "{[]}"; 83 | 84 | Console.WriteLine(IsValid(s)); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /剑指offer/NumberOf1.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 二进制中1的个数 4 | 5 | 题目描述: 6 | 输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。 7 | 8 | 代码结构: 9 | class Solution 10 | { 11 | public int NumberOf1(int n) 12 | { 13 | // write code here 14 | } 15 | } 16 | 17 | 补充: 18 | 正数的补码等于其原码 19 | 负数的补码等于其原码按位取反后(除了最高位)加1 20 | */ 21 | using System; 22 | namespace NumberOf1 { 23 | 24 | class Solution { 25 | 26 | /// 27 | /// 解法1 28 | /// 基本思路: 29 | /// 对于本题,首先想到的是将目标数一直右移,然后将该数和1相与,计算1的个数,直到该二进制数为0为止 30 | /// 但是考虑负数的情况,和正数右移最高位补0不同的是,负数右移最高位补1,这样就会有无数个1,导致循环无法停止 31 | /// 既然将目标数右移和1与行不通,那么我们可以反过来 32 | /// 将1不断左移(从最低位到最高位每一位依次是1,其他位是0),然后和目标数相与来求1的个数 33 | /// 1的特点,就是最低位是1,其他位是0。数值和1与可以判断自己最低位的情况 34 | /// 将1不断左移,可以让每位都是1(其他位是0),数值和它与可以判断自己每位的情况 35 | /// 1在不断左移时,到最高位时时(一个较大的负数),移过最高位后就是0 36 | /// 37 | 38 | public int NumberOf1(int n) 39 | { 40 | int unit = 1, count = 0; 41 | while(unit != 0){ 42 | if((n & unit) != 0){ 43 | count ++; 44 | } 45 | unit <<= 1; 46 | } 47 | return count; 48 | } 49 | 50 | /// 51 | /// 解法2 52 | /// 基本思路: 53 | /// 上面解法1的时间复杂度是O(n的位数),n有所少位就要循环多少次。可以利用一个小技巧,降低算法的时间复杂度。 54 | /// 对于数值n,将n - 1后再和n相与,得到的值相当于将n从右边数的第一个1变成0。 55 | /// n的二进制表示中有多少个1,就能变多少次。时间复杂度可以优化为O(n中1的个数) 56 | /// 详细介绍 https://www.cnblogs.com/iwiniwin/p/11058255.html 57 | /// 58 | 59 | public int NumberOf12(int n){ 60 | int count = 0; 61 | while(n != 0){ 62 | count ++; 63 | n &= (n - 1); 64 | } 65 | return count; 66 | } 67 | 68 | public void Test() { 69 | 70 | int n = 6; 71 | n = 1; 72 | n = 0; 73 | n = -1; 74 | n = -20; 75 | 76 | // Console.WriteLine(NumberOf1(n)); 77 | Console.WriteLine(NumberOf12(n)); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /剑指offer/LastRemaining.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 孩子们的游戏(圆圈中最后剩下的数) 4 | 5 | 题目描述: 6 | 每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。 7 | HF作为牛客的资深元老,自然也准备了一些小游戏。 8 | 其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。 9 | 每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中 10 | 从他的下一个小朋友开始,继续0...m-1报数....这样下去....直到剩下最后一个小朋友,可以不用表演 11 | 并且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!^_^)。 12 | 请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1) 13 | 14 | 如果没有小朋友,请返回-1 15 | 16 | 代码结构: 17 | class Solution 18 | { 19 | public int LastRemaining_Solution(int n, int m) 20 | { 21 | // write code here 22 | } 23 | } 24 | */ 25 | using System; 26 | namespace LastRemaining { 27 | 28 | class Solution { 29 | 30 | /// 31 | /// 解法 32 | /// 基本思路: 33 | /// 约瑟夫环问题,遍历到数组末尾时再从头开始遍历,模拟环 34 | /// 使用array数组记录每个编号是否被移除 0表示未移除 -1表示移除 35 | /// while循环不断遍历array数组,直到所有元素值都为-1,表示全部被移除 36 | /// 最后一个被移除的元素就是要找的小朋友 37 | /// 38 | 39 | public int LastRemaining_Solution(int n, int m) 40 | { 41 | int[] array = new int[n]; 42 | int count = 0, temp = 0; 43 | int index = 0; 44 | while(count < n){ 45 | if(index == n){ 46 | index = 0; 47 | } 48 | if(array[index] == 0){ 49 | if(temp == m - 1){ 50 | array[index] = -1; 51 | temp = 0; 52 | count ++; 53 | }else{ 54 | temp ++; 55 | } 56 | } 57 | index ++; 58 | } 59 | return index - 1; 60 | } 61 | 62 | 63 | public void Test() { 64 | 65 | int n = 1; 66 | // n = 0; 67 | // n = 5; 68 | // n = 6; 69 | 70 | int m = 1; 71 | // m = 2; 72 | // m = 3; 73 | 74 | Console.WriteLine(LastRemaining_Solution(n, m)); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /剑指offer/PrintFromTopToBottom.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 从上往下打印二叉树 4 | 5 | 题目描述: 6 | 从上往下打印出二叉树的每个节点,同层节点从左至右打印。 7 | 8 | 代码结构: 9 | class Solution 10 | { 11 | public List PrintFromTopToBottom(TreeNode root) 12 | { 13 | // write code here 14 | } 15 | } 16 | */ 17 | using System; 18 | using System.Collections.Generic; 19 | namespace PrintFromTopToBottom { 20 | 21 | public class TreeNode 22 | { 23 | public int val; 24 | public TreeNode left; 25 | public TreeNode right; 26 | public TreeNode (int x) 27 | { 28 | val = x; 29 | } 30 | } 31 | 32 | class Solution { 33 | 34 | /// 35 | /// 解法 36 | /// 基本思路: 37 | /// 层次遍历,利用一个辅助队列,队列中依次保存二叉树的根节点,左节点,右节点 38 | /// 当出列一个节点的同时,入列该节点的左右子节点,根据队列的先进先出特性,实现从上到下,从左到右的顺序 39 | /// 40 | 41 | public List PrintFromTopToBottom(TreeNode root) 42 | { 43 | List list = new List(); 44 | Queue queue = new Queue(); 45 | queue.Enqueue(root); 46 | while(queue.Count > 0){ 47 | TreeNode node = queue.Dequeue(); 48 | if(node != null){ 49 | list.Add(node.val); 50 | queue.Enqueue(node.left); 51 | queue.Enqueue(node.right); 52 | } 53 | } 54 | return list; 55 | } 56 | 57 | public void Print(List list){ 58 | if(list == null){ 59 | Console.WriteLine("null"); 60 | return; 61 | } 62 | foreach(int item in list) 63 | Console.WriteLine(item); 64 | } 65 | 66 | public void Test() { 67 | 68 | TreeNode root = new TreeNode(1); 69 | root.left = new TreeNode(2); 70 | root.left.left = new TreeNode(2); 71 | root.left.left.left = new TreeNode(5); 72 | root.right = new TreeNode(3); 73 | root.right.left = new TreeNode(4); 74 | root.right.left.right = new TreeNode(4); 75 | 76 | Print(PrintFromTopToBottom(root)); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /剑指offer/GetNumberOfK.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 数字在排序数组中出现的次数 4 | 5 | 题目描述: 6 | 统计一个数字在排序数组中出现的次数。 7 | 8 | 代码结构: 9 | class Solution 10 | { 11 | public int GetNumberOfK(int[] data, int k) 12 | { 13 | // write code here 14 | } 15 | } 16 | */ 17 | using System; 18 | namespace GetNumberOfK { 19 | 20 | class Solution { 21 | 22 | /// 23 | /// 解法,二分查找 24 | /// 在排序数组中查找,首先想到的是二分查找 25 | /// 由于数组中的数字都是整数,所以可以通过分别查找k+0.5和k-0.5这两个数字应该插入的位置 26 | /// 两个位置的差值就是数字k的个数 27 | /// 由于题目未说明排序数组是递增还是递减,所以通过increase标志判断 28 | /// 29 | 30 | public int Find(int[] data, double k){ 31 | int left = 0, right = data.Length - 1; 32 | int mid = 0; 33 | bool increase = true; 34 | if(data[left] > data[right]){ 35 | increase = false; 36 | } 37 | while(left <= right){ 38 | mid = (left + right) / 2; 39 | if(data[mid] < k){ 40 | if(increase){ 41 | left = mid + 1; 42 | }else{ 43 | right = mid - 1; 44 | } 45 | 46 | }else if(data[mid] > k){ 47 | if(increase){ 48 | right = mid - 1; 49 | }else{ 50 | left = mid + 1; 51 | } 52 | } 53 | } 54 | return increase ? left : -left; 55 | } 56 | 57 | public int GetNumberOfK(int[] data, int k) 58 | { 59 | if(data == null || data.Length == 0){ 60 | return 0; 61 | } 62 | return Find(data, k + 0.5) - Find(data, k - 0.5); 63 | } 64 | 65 | public void Test() { 66 | int[] data = new int[]{1, 2, 3, 3, 4}; 67 | // data = new int[]{3, 3, 3, 3, 3}; 68 | // data = null; 69 | // data = new int[]{}; 70 | // data = new int[]{4, 3, 3, 2, 1}; 71 | int k = 3; 72 | 73 | Console.WriteLine(GetNumberOfK(data, k)); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /剑指offer/IsBalanced.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 平衡二叉树 4 | 5 | 题目描述: 6 | 输入一棵二叉树,判断该二叉树是否是平衡二叉树。 7 | 8 | 代码结构: 9 | class Solution 10 | { 11 | public bool IsBalanced_Solution(TreeNode pRoot) 12 | { 13 | // write code here 14 | } 15 | } 16 | 17 | 补充: 18 | 平衡二叉树定义: 19 | 1. 可以是空树 20 | 2. 假如不是空树,任何一个节点的左子树与右子树都是平衡二叉树,并且高度之差的绝对值不超过1 21 | */ 22 | using System; 23 | namespace IsBalanced { 24 | 25 | public class TreeNode 26 | { 27 | public int val; 28 | public TreeNode left; 29 | public TreeNode right; 30 | public TreeNode (int x) 31 | { 32 | val = x; 33 | } 34 | } 35 | 36 | class Solution { 37 | 38 | /// 39 | /// 解法,递归 40 | /// 基本思路: 41 | /// 按照平衡二叉树的定义,递归计算左右子树的高度差是否大于1来判断是否是平衡二叉树 42 | /// 如果发现某棵树的左右子树高度差大于1,即不是平衡二叉树,则直接返回-1,终止递归 43 | /// 44 | 45 | public int TreeDepth(TreeNode node){ 46 | if(node == null){ 47 | return 0; 48 | } 49 | int left = TreeDepth(node.left); 50 | if(left == - 1){ 51 | return -1; 52 | } 53 | int right = TreeDepth(node.right); 54 | if(right == - 1){ 55 | return -1; 56 | } 57 | if(Math.Abs(left - right) > 1){ 58 | return -1; 59 | } 60 | return left > right ? left + 1 : right + 1; 61 | } 62 | 63 | public bool IsBalanced_Solution(TreeNode pRoot) 64 | { 65 | if(TreeDepth(pRoot) == -1){ 66 | return false; 67 | } 68 | return true; 69 | } 70 | 71 | public void Test() { 72 | TreeNode node = new TreeNode(1); 73 | // node = null; 74 | node.left = new TreeNode(2); 75 | node.left.left = new TreeNode(3); 76 | node.right = new TreeNode(4); 77 | node.right.right = new TreeNode(5); 78 | // node.right.right.right = new TreeNode(6); 79 | node.right.right.left = new TreeNode(7); 80 | 81 | Console.WriteLine(IsBalanced_Solution(node)); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /剑指offer/ReverseList.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 反转链表 4 | 5 | 题目描述: 6 | 输入一个链表,反转链表后,输出新链表的表头。 7 | 8 | 代码结构: 9 | class Solution 10 | { 11 | public ListNode ReverseList(ListNode pHead) 12 | { 13 | // write code here 14 | } 15 | } 16 | */ 17 | using System; 18 | namespace ReverseList { 19 | 20 | public class ListNode 21 | { 22 | public int val; 23 | public ListNode next; 24 | public ListNode (int x) 25 | { 26 | val = x; 27 | } 28 | } 29 | 30 | class Solution { 31 | 32 | /// 33 | /// 解法1 34 | /// 基本思路: 35 | /// 使用三个指针pHead, last, next 36 | /// pHead记录当前节点,last记录上一个节点,next记录下一个节点 37 | /// 首先使用next保存当前节点的下一个节点,然后将当前节点的下一个节点指向last,实现反转 38 | /// 39 | 40 | public ListNode ReverseList(ListNode pHead) 41 | { 42 | ListNode last = null, next = null; 43 | while(pHead != null){ 44 | 45 | next = pHead.next; 46 | 47 | pHead.next = last; 48 | 49 | last = pHead; 50 | 51 | pHead = next; 52 | } 53 | return last; 54 | } 55 | 56 | /// 57 | /// 解法2,递归 58 | /// 基本思路: 59 | /// 通过不断递归,先从链表的尾节点开始反转 60 | /// 然后利用递归的回溯实现按照从尾到头的顺序反转每个节点 61 | /// 62 | 63 | public ListNode ReverseList2(ListNode pHead) 64 | { 65 | if(pHead == null || pHead.next == null) return pHead; 66 | ListNode node = ReverseList2(pHead.next); 67 | pHead.next.next = pHead; 68 | pHead.next = null; 69 | return node; 70 | } 71 | 72 | public void Print(ListNode head){ 73 | while(head != null){ 74 | Console.WriteLine(head.val); 75 | head = head.next; 76 | } 77 | } 78 | 79 | public void Test() { 80 | 81 | ListNode pHead = new ListNode(1); 82 | pHead.next = new ListNode(2); 83 | pHead.next.next = new ListNode(3); 84 | pHead.next.next.next = new ListNode(4); 85 | // pHead = null; 86 | 87 | // Print(ReverseList(pHead)); 88 | Print(ReverseList2(pHead)); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Leetcode/MaxArea.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 盛最多水的容器 4 | 5 | 题目描述: 6 | 给你 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) 。 7 | 在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。 8 | 找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。 9 | 10 | 说明: 11 | 你不能倾斜容器,且 n 的值至少为 2。 12 | 13 | 示例: 14 | 输入:[1,8,6,2,5,4,8,3,7] 15 | 输出:49 16 | 17 | 代码结构: 18 | public class Solution { 19 | public int MaxArea(int[] height) { 20 | 21 | } 22 | } 23 | 24 | 题目链接: 25 | https://leetcode-cn.com/problems/container-with-most-water/ 26 | 27 | 官方题解: 28 | https://leetcode-cn.com/problems/container-with-most-water/solution/sheng-zui-duo-shui-de-rong-qi-by-leetcode-solution/ 29 | */ 30 | using System; 31 | using System.Collections.Generic; 32 | namespace MaxArea { 33 | 34 | class Solution { 35 | 36 | /// 37 | /// 解法,双指针 38 | /// 基本思路: 39 | /// 使用左指针i指向数组左边界,使用右指针j指向数组右边界 40 | /// i,j就表示容器可能的边界,通过不断移动i,j,找出i,j构成容器的最大容量 41 | /// 那么i,j应该怎样移动呢 42 | /// 当height[i] < height[j]时,i应该向右移动。 43 | /// 原因是,此时的容器容量是 area = min(height[i], height[j]) * (j - i) = height[i] * (j - i) 44 | /// 如果i不动,j向左移动,则容量只会比area小,因为 (j - i)变小了,而 min(height[i], height[j]) 仍然是小于等于 height[i] 45 | /// 即无论怎么移动右指针,得到的容器容量都小于移动前容器的容量,也就是说,这个左指针对应的数不会作为容器的边界了,可以丢弃 46 | /// 同理当height[i] > height[j]时,j应该向左移动。 47 | /// 当height[i] == height[j]时,移动i还是移动j都是一样的 48 | /// 49 | 50 | public int MaxArea(int[] height) { 51 | int ans = 0; 52 | int i = 0, j = height.Length - 1; 53 | while(i < j){ 54 | int area = (j - i) * Math.Min(height[i], height[j]); 55 | if(area > ans) ans = area; 56 | if(height[i] < height[j]) 57 | i ++; 58 | else 59 | j --; 60 | } 61 | return ans; 62 | } 63 | 64 | public void Test() { 65 | int[] height = new int[]{1, 8, 6, 2, 5, 4, 8, 3, 7}; 66 | // height = new int[]{2, 4}; 67 | // height = new int[]{1, 1, 1, 1, 1, 1, 1, 1, 1, 7, 9}; 68 | // height = new int[]{7, 9, 1, 1, 1, 1, 1, 1, 1, 1, 1}; 69 | // height = new int[]{5, 1, 1, 3, 1, 1, 1, 1, 1, 20, 9}; 70 | 71 | Console.WriteLine(MaxArea(height)); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Leetcode/ReverseString.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 反转字符串 4 | 5 | 题目描述: 6 | 编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。 7 | 不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。 8 | 你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符。 9 | 10 | 示例 1: 11 | 输入:["h","e","l","l","o"] 12 | 输出:["o","l","l","e","h"] 13 | 14 | 示例 2: 15 | 输入:["H","a","n","n","a","h"] 16 | 输出:["h","a","n","n","a","H"] 17 | 18 | 代码结构: 19 | public class Solution { 20 | public void ReverseString(char[] s) { 21 | 22 | } 23 | } 24 | 25 | 题目链接: 26 | https://leetcode-cn.com/problems/reverse-string/ 27 | */ 28 | using System; 29 | using System.Collections.Generic; 30 | namespace ReverseString { 31 | 32 | class Solution { 33 | 34 | /// 35 | /// 解法1 36 | /// 基本思路: 37 | /// 第一个字符与倒数第一个字符交换,第二个与倒数第二个交换,第三个与倒数第三个。。。 38 | /// 对于反转字符数组的所有字符,实际上只需要交换 数组长度的一半 次即可 39 | /// 40 | 41 | public void ReverseString(char[] s) { 42 | int len = s.Length; 43 | for(int i = 0; i < len / 2; i ++){ 44 | char temp = s[i]; 45 | s[i] = s[len - 1 - i]; 46 | s[len - 1 - i] = temp; 47 | } 48 | } 49 | 50 | /// 51 | /// 解法2,双指针 52 | /// 基本思路: 53 | /// 使用left指针指向数组头部,right指针指向数组尾部 54 | /// 交换left与right位置的元素同时,left向右移动,right向左移动 55 | /// 直到两个指针相遇则所有元素替换完毕 56 | /// 57 | 58 | public void ReverseString2(char[] s) { 59 | int left = 0, right = s.Length - 1; 60 | while(left < right){ 61 | char temp = s[left]; 62 | s[left++] = s[right]; 63 | s[right--] = temp; 64 | } 65 | } 66 | 67 | public void Print(char[] s){ 68 | foreach (var c in s) 69 | { 70 | Console.Write(c + " "); 71 | } 72 | Console.WriteLine(); 73 | } 74 | 75 | public void Test() { 76 | char[] s = new char[]{'h','e','l','l','o'}; 77 | // s = new char[]{'H', 'a', 'n', 'n', 'a', 'h'}; 78 | // s = new char[]{}; 79 | // s = new char[]{'a'}; 80 | 81 | // ReverseString(s); 82 | ReverseString2(s); 83 | 84 | Print(s); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /剑指offer/MovingCount.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 机器人的运动范围 4 | 5 | 题目描述: 6 | 地上有一个m行和n列的方格。 7 | 一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格, 8 | 但是不能进入行坐标和列坐标的数位之和大于k的格子。 9 | 例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。 10 | 但是,它不能进入方格(35,38),因为3+5+3+8 = 19。 11 | 请问该机器人能够达到多少个格子? 12 | 13 | 代码结构: 14 | class Solution 15 | { 16 | public int movingCount(int threshold, int rows, int cols) 17 | { 18 | // write code here 19 | } 20 | } 21 | */ 22 | using System; 23 | namespace MovingCount { 24 | 25 | class Solution { 26 | 27 | /// 28 | /// 解法 29 | /// 基本思路: 30 | /// 利用递归,从0,0点,开始向上下左右开始搜索 31 | /// 利用flag数组记录搜索过的节点,置为true。 32 | /// 利用count统计进入的格子个数 33 | /// 注意,这里在回溯的时候是不用将标记还原的 34 | /// 35 | 36 | int count = 0; 37 | 38 | public int Calc(int num) { 39 | int sum = 0; 40 | while(num > 0){ 41 | sum += num % 10; 42 | num /= 10; 43 | } 44 | return sum; 45 | } 46 | 47 | public void MovingCountImpl(int threshold, int rows, int cols, int row, int col, bool[,] flag) { 48 | 49 | if(row < 0 || row >= rows || col < 0 || col >= cols || flag[row, col]){ 50 | return; 51 | } 52 | 53 | if(Calc(row) + Calc(col) > threshold){ 54 | return; 55 | } 56 | count ++; 57 | flag[row, col] = true; 58 | MovingCountImpl(threshold, rows, cols, row, col + 1, flag); 59 | MovingCountImpl(threshold, rows, cols, row, col - 1, flag); 60 | MovingCountImpl(threshold, rows, cols, row + 1, col, flag); 61 | MovingCountImpl(threshold, rows, cols, row - 1, col, flag); 62 | } 63 | 64 | public int MovingCount(int threshold, int rows, int cols) 65 | { 66 | bool[,] flag = new bool[rows, cols]; 67 | count = 0; 68 | MovingCountImpl(threshold, rows, cols, 0, 0, flag); 69 | return count; 70 | } 71 | 72 | public void Test() { 73 | 74 | int threshold = 9; 75 | int rows = 100; 76 | int cols = 100; 77 | 78 | Console.WriteLine(MovingCount(threshold, rows, cols)); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /剑指offer/IsNumeric.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 表示数值的字符串 4 | 5 | 题目描述: 6 | 请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。 7 | 例如,字符串"+100","5e2","-123","3.1416"和"-1E-16"都表示数值。 8 | 但是"12e","1a3.14","1.2.3","+-5"和"12e+4.3"都不是。 9 | 10 | 代码结构: 11 | class Solution 12 | { 13 | public bool isNumeric(char[] str) 14 | { 15 | // write code here 16 | } 17 | } 18 | */ 19 | using System; 20 | using System.Text.RegularExpressions; 21 | namespace IsNumeric { 22 | 23 | class Solution { 24 | 25 | /// 26 | /// 解法1 27 | /// 基本思路: 28 | /// 根据数值字符串标准判断,每个字符满足如下条件 29 | /// 1. 字符是'0'-'9' 30 | /// 2. 字符是'+'或'-',只能出现在首位或'e'/'E'的后面 31 | /// 3. 字符是'e'/'E',只能出现一次,且不能在首尾 32 | /// 4. 字符是'.',只能出现一次,且不能在首位(可以在尾部),且不能在'e'/'E'之后出现 33 | /// 34 | 35 | public bool IsNumeric(string str) 36 | { 37 | if(string.IsNullOrEmpty(str)) return false; 38 | int dotCount = 0, eCount = 0; 39 | for(int i = 0; i < str.Length; i ++){ 40 | if(str[i] >= '0' && str[i] <= '9') continue; 41 | if(str[i] == '+' || str[i] == '-'){ 42 | if(i == 0 || (i > 0 && (str[i - 1] == 'e' || str[i - 1] == 'E'))) continue; 43 | } 44 | if((str[i] == 'e' || str[i] == 'E') && i > 0 && i < str.Length - 1 && eCount == 0){ 45 | eCount ++; 46 | continue; 47 | } 48 | if(str[i] == '.' && i > 0 && i < str.Length && eCount == 0 && dotCount == 0){ 49 | dotCount ++; 50 | continue; 51 | } 52 | return false; 53 | } 54 | return true; 55 | } 56 | 57 | /// 58 | /// 解法2 59 | /// 基本思路: 60 | /// 使用正则表达式进行匹配 61 | /// 62 | 63 | public bool IsNumeric2(string str) 64 | { 65 | return Regex.IsMatch(new string(str), @"^[+-]?\d*(\.\d+)?([eE][+-]?\d+)?$"); 66 | } 67 | 68 | /// 69 | /// TODO 状态迁移表解法 70 | /// 71 | 72 | public void Test() { 73 | string str = " -.12"; 74 | 75 | Console.WriteLine(IsNumeric(str)); 76 | // Console.WriteLine(IsNumeric2(str)); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /排序算法/BubbleSort.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 算法名称: 3 | 冒泡排序 4 | 5 | 时间复杂度:O(n^2) 6 | 7 | 空间复杂度:O(1) 8 | 9 | 最好情况: 10 | 正序有序时,普通冒泡排序仍是O(n^2),优化后的冒泡排序是O(n) 11 | 12 | 最坏情况: 13 | 逆序有序时,O(n^2) 14 | 15 | 稳定性:稳定 16 | 17 | 优点:简单,稳定,空间复杂度低 18 | 19 | 缺点:时间复杂度高,效率不好 20 | */ 21 | using System; 22 | namespace BubbleSort { 23 | 24 | class Solution { 25 | 26 | /// 27 | /// 基本思想: 28 | /// 从序列的末尾开始,针对每一个元素,与其相邻的元素进行比较,若不满足条件,则进行交换 29 | /// 这样每一次都会有一个相对最小的元素如气泡一般逐渐往上漂浮至水面 30 | /// 31 | 32 | public void BubbleSort(int[] array){ 33 | for(int i = 0; i < array.Length - 1; i ++){ 34 | for(int j = array.Length - 1; j > i ; j --){ 35 | if(array[j] < array[j - 1]){ 36 | Swap(array, j, j - 1); 37 | } 38 | } 39 | } 40 | } 41 | 42 | /// 43 | /// 基本思想: 44 | /// 优化正序有序时的复杂度,当第一次冒泡排序后没有发生位置交换 45 | /// 则说明所有元素都已经正确的在其位置上,结束算法即可 46 | /// 47 | 48 | public void BubbleSortOptimize(int[] array){ 49 | for(int i = 0; i < array.Length - 1; i ++){ 50 | bool swapTag = false; // 标志位,判断每完成一趟冒牌排序是否发生交换 51 | for(int j = array.Length - 1; j > i; j --){ 52 | if(array[j] < array[j - 1]){ 53 | Swap(array, j, j -1); 54 | swapTag = true; 55 | } 56 | } 57 | if(!swapTag){ 58 | break; 59 | } 60 | } 61 | } 62 | 63 | public void Swap(int[] array, int i, int j){ 64 | int temp = array[i]; 65 | array[i] = array[j]; 66 | array[j] = temp; 67 | } 68 | 69 | public void Test() { 70 | 71 | int[] array = new int[]{1, 3, 2, 4}; 72 | array = new int[]{1, 1, 3, 3, 4, 2, 2, 6, 1, 9}; 73 | 74 | // BubbleSort(array); 75 | BubbleSortOptimize(array); 76 | 77 | Print(array); 78 | } 79 | 80 | public void Print(int[] array){ 81 | foreach (int item in array) 82 | { 83 | Console.Write(item + " "); 84 | } 85 | Console.WriteLine(); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Leetcode/RectangleOverlap.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 矩形重叠 4 | 5 | 题目描述: 6 | 矩形以列表 [x1, y1, x2, y2] 的形式表示,其中 (x1, y1) 为左下角的坐标,(x2, y2) 是右上角的坐标。矩形的上下边平行于 x 轴,左右边平行于 y 轴。 7 | 如果相交的面积为正 ,则称两矩形重叠。需要明确的是,只在角或边接触的两个矩形不构成重叠。 8 | 给出两个矩形 rec1 和 rec2 。如果它们重叠,返回 true;否则,返回 false 。 9 | 10 | 示例1: 11 | 输入:rec1 = [0,0,2,2], rec2 = [1,1,3,3] 12 | 输出:true 13 | 14 | 示例2: 15 | 输入:rec1 = [0,0,1,1], rec2 = [1,0,2,1] 16 | 输出:false 17 | 18 | 示例3: 19 | 输入:rec1 = [0,0,1,1], rec2 = [2,2,3,3] 20 | 输出:false 21 | 22 | 代码结构: 23 | public class Solution { 24 | public bool IsRectangleOverlap(int[] rec1, int[] rec2) { 25 | 26 | } 27 | } 28 | 29 | 题目链接: 30 | https://leetcode-cn.com/problems/rectangle-overlap/ 31 | */ 32 | using System; 33 | using System.Collections.Generic; 34 | namespace RectangleOverlap { 35 | 36 | class Solution { 37 | 38 | /// 39 | /// 解法1 40 | /// 基本思路: 41 | /// 反向思维,检查两个矩形的位置,如果这两个矩形不重叠,则矩形1可能在矩形的左侧,上侧,右侧,或者下侧 42 | /// 如果第一个矩形在第二个矩形的左侧且不重叠,则矩形1的右下角横坐标必须小于等于矩形2左上角的横坐标 43 | /// 同理,可判断上侧,右侧,以及下侧 44 | /// 如果这些条件两个矩形没有一个满足的,就说明两个矩形重叠 45 | /// 46 | public bool IsRectangleOverlap(int[] rec1, int[] rec2) { 47 | // left top right bottom 48 | return !(rec1[2] <= rec2[0] || rec1[1] >= rec2[3] || rec1[0] >= rec2[2] || rec1[3] <= rec2[1] || 49 | rec1[0] == rec1[2] || rec1[1] == rec1[3] || rec2[0] == rec2[2] || rec2[1] == rec2[3]); 50 | } 51 | 52 | /// 53 | /// 解法2 54 | /// 基本思路: 55 | /// 如果两个矩形重叠,则两个矩形的水平边,投影到x轴以后的两条线段也一定重合 56 | /// 如果两个矩形重叠,则两个矩形的垂直边,投影到y轴以后的两条线段也一定重合 57 | /// 所以最终将问题转换为求解两条线段是否重合的问题 58 | /// 59 | public bool IsRectangleOverlap2(int[] rec1, int[] rec2) { 60 | return Math.Max(rec1[0], rec2[0]) < Math.Min(rec1[2], rec2[2]) && 61 | Math.Max(rec1[1], rec2[1]) < Math.Min(rec1[3], rec2[3]); 62 | } 63 | 64 | public void Test() { 65 | int[] rec1 = new int[]{0, 0, 2, 2}; 66 | int[] rec2 = new int[]{1, 1, 3, 3}; 67 | 68 | rec1 = new int[]{0, 0, 1, 1}; 69 | rec2 = new int[]{1, 0, 2, 1}; 70 | 71 | // rec1 = new int[]{0, 0, 2, 2}; 72 | // rec2 = new int[]{2, 2, 3, 3}; 73 | 74 | // Console.WriteLine(IsRectangleOverlap(rec1, rec2)); 75 | Console.WriteLine(IsRectangleOverlap2(rec1, rec2)); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /排序算法/BucketSort.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 算法名称: 3 | 桶排序 4 | 5 | 时间复杂度:O(n) 6 | 7 | 空间复杂度:O(m + n),m表示桶的个数 8 | 9 | 最好情况: 10 | O(n),当待排序列的元素是被均匀分配到桶中时,是线性时间O(n) 11 | 12 | 最坏情况: 13 | 所有元素都被分配到同一个桶中,退化为普通排序。各个桶内的数据越少,排序所用的时间也越少,但相应的空间消耗就会增大 14 | 15 | 稳定性:稳定 16 | 17 | 优点:稳定,突破了基于比较排序的下限 18 | 19 | 缺点:需要额外的辅助空间,需要好的映射函数 20 | */ 21 | using System; 22 | using System.Collections.Generic; 23 | namespace BucketSort { 24 | 25 | class Solution { 26 | 27 | /// 28 | /// 基本思想: 29 | /// 将待排序列通过事先设定好的映射函数分到有限数量的桶里。每个桶再进行排序(可以使用别的排序算法,比如快速排序)。 30 | /// 1. 准备有限数量的空桶 31 | /// 2. 遍历待排序列,将每个元素通过映射函数分配到对应的桶中 32 | /// 3. 对每个不是空的桶进行排序 33 | /// 4. 从每个不是空的桶中再依次把元素放回到原来的序列中 34 | /// 35 | 36 | public void BucketSort(int[] array){ 37 | int max = array[0], min = array[0]; 38 | for(int i = 1; i < array.Length; i ++){ 39 | if(array[i] > max) max = array[i]; 40 | if(array[i] < min) min = array[i]; 41 | } 42 | List[] buckets = new List[Fun(max, min, array.Length) + 1]; 43 | for(int i = 0; i < buckets.Length; i ++){ 44 | buckets[i] = new List(); 45 | } 46 | for(int i = 0; i < array.Length; i ++){ 47 | buckets[Fun(array[i], min, array.Length)].Add(array[i]); 48 | } 49 | int index = 0; 50 | for(int i = 0; i < buckets.Length; i ++){ 51 | // 桶内的排序借助了Sort方法,也可以使用其他排序方法 52 | buckets[i].Sort(); 53 | foreach(int item in buckets[i]){ 54 | array[index ++] = item; 55 | } 56 | } 57 | } 58 | 59 | public int Fun(int value, int minValue, int length){ 60 | return (value - minValue) / length; 61 | } 62 | 63 | public void Test() { 64 | 65 | int[] array = new int[]{4, 1, 0, 2, 3}; 66 | 67 | // array = new int[]{10, 25, 0, 0, 0, 58, 90, 1, 1, 0, 1, 952, 7896, 3, 44}; 68 | // array = new int[]{7896, 0}; 69 | 70 | BucketSort(array); 71 | 72 | Print(array); 73 | } 74 | 75 | public void Print(int[] array){ 76 | foreach (int item in array) 77 | { 78 | Console.Write(item + " "); 79 | } 80 | Console.WriteLine(); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Leetcode/ReverseWords.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 反转字符串中的单词 III 4 | 5 | 题目描述: 6 | 给定一个字符串,你需要反转字符串中每个单词的字符顺序,同时仍保留空格和单词的初始顺序。 7 | 8 | 示例 1: 9 | 输入: "Let's take LeetCode contest" 10 | 输出: "s'teL ekat edoCteeL tsetnoc" 11 | 12 | 注意: 13 | 在字符串中,每个单词由单个空格分隔,并且字符串中不会有任何额外的空格。 14 | 15 | 代码结构: 16 | public class Solution { 17 | public string ReverseWords(string s) { 18 | 19 | } 20 | } 21 | 22 | 题目链接: 23 | https://leetcode-cn.com/problems/reverse-words-in-a-string-iii/ 24 | */ 25 | using System; 26 | using System.Collections.Generic; 27 | namespace ReverseWords { 28 | 29 | class Solution { 30 | 31 | /// 32 | /// 解法1 33 | /// 基本思路: 34 | /// 利用Split函数将字符串根据" "拆分成多个子字符串 35 | /// 依次翻转子字符串的顺序,然后再用Join函数通过" "连接起来 36 | /// 37 | 38 | public string Reverse(string str){ 39 | char[] s = str.ToCharArray(); 40 | int left = 0, right = s.Length - 1; 41 | while(left < right){ 42 | char temp = s[left]; 43 | s[left ++] = s[right]; 44 | s[right --] = temp; 45 | } 46 | return new string(s); 47 | } 48 | 49 | public string ReverseWords(string s) { 50 | string[] strs = s.Split(" "); 51 | for(int i = 0; i < strs.Length; i ++){ 52 | strs[i] = Reverse(strs[i]); 53 | } 54 | return string.Join(" ", strs); 55 | } 56 | 57 | /// 58 | /// 解法2 59 | /// 基本思路: 60 | /// 循环遍历,通过' '找到每一个单词,然后反转该单词 61 | /// 62 | 63 | public void Reverse(char[] cs, int i, int j){ 64 | while(i < j){ 65 | char temp = cs[i]; 66 | cs[i ++] = cs[j]; 67 | cs[j --] = temp; 68 | } 69 | } 70 | 71 | public string ReverseWords2(string s) { 72 | char[] cs = s.ToCharArray(); 73 | int i = 0, j = 0; 74 | while(i < cs.Length){ 75 | while(j < cs.Length && cs[j] != ' ') 76 | j ++; 77 | Reverse(cs, i, j - 1); 78 | i = ++j; 79 | } 80 | return new string(cs); 81 | } 82 | 83 | public void Test() { 84 | string s = "Let's take LeetCode contest"; 85 | // s = ""; 86 | // s = " a "; 87 | 88 | // Console.WriteLine(ReverseWords(s)); 89 | Console.WriteLine(ReverseWords2(s)); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /剑指offer/LeftRotateString.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 左旋转字符串 4 | 5 | 题目描述: 6 | 汇编语言中有一种移位指令叫做循环左移(ROL),现在有个简单的任务,就是用字符串模拟这个指令的运算结果。 7 | 对于一个给定的字符序列S,请你把其循环左移K位后的序列输出。 8 | 例如,字符序列S=”abcXYZdef”,要求输出循环左移3位后的结果,即“XYZdefabc”。是不是很简单?OK,搞定它! 9 | 10 | 代码结构: 11 | class Solution 12 | { 13 | public string LeftRotateString(string str, int n) 14 | { 15 | // write code here 16 | } 17 | } 18 | */ 19 | using System; 20 | namespace LeftRotateString { 21 | 22 | class Solution { 23 | 24 | /// 25 | /// 解法1 26 | /// 基本思路: 27 | /// 利用对字符串的长度求余,处理左移位数大于字符串长度的情况 28 | /// 小于字符串长度的左移,通过分割字符串后调转位置得到 29 | /// 30 | 31 | public string LeftRotateString(string str, int n) 32 | { 33 | if(str == null || str.Length == 0){ 34 | return str; 35 | } 36 | int index = n % str.Length; 37 | char[] array = str.ToCharArray(); 38 | return new string(array, index, str.Length - index) + new string(array, 0, index); 39 | } 40 | 41 | /// 42 | /// 解法2 43 | /// 基本思路: 44 | /// 同样利用对字符串的长度求余,处理左移位数大于字符串长度的情况 45 | /// 对于小于字符串长度的左移,利用 XY的翻转 = YX = (X的翻转 + Y的翻转)的翻转 得到 46 | /// 47 | 48 | public void Reverse(char[] array, int i, int j){ 49 | for(int m = i, n = j; m < n; m ++, n --){ 50 | char temp = array[m]; 51 | array[m] = array[n]; 52 | array[n] = temp; 53 | } 54 | } 55 | 56 | public string LeftRotateString2(string str, int n) 57 | { 58 | if(str == null || str.Length == 0){ 59 | return str; 60 | } 61 | int index = n % str.Length; 62 | char[] array = str.ToCharArray(); 63 | Reverse(array, 0, index - 1); 64 | Reverse(array, index, array.Length - 1); 65 | Reverse(array, 0, array.Length - 1); 66 | return new string(array); 67 | } 68 | 69 | public void Test() { 70 | 71 | string str = "abcXYZdef"; 72 | // str = null; 73 | // str = ""; 74 | 75 | int n = 3; 76 | // n = 0; 77 | // n = 1; 78 | // n = 9; 79 | // n = 10; 80 | 81 | Console.WriteLine(LeftRotateString(str, n)); 82 | // Console.WriteLine(LeftRotateString2(str, n)); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /剑指offer/ReOrderArray.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 调整数组顺序使奇数位于偶数前面 4 | 5 | 题目描述: 6 | 输入一个整数数组,实现一个函数来调整该数组中数字的顺序, 7 | 使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分, 8 | 并保证奇数和奇数,偶数和偶数之间的相对位置不变。 9 | 10 | 代码结构: 11 | class Solution 12 | { 13 | public int[] reOrderArray(int[] array) 14 | { 15 | // write code here 16 | } 17 | } 18 | */ 19 | using System; 20 | namespace ReOrderArray { 21 | 22 | class Solution { 23 | 24 | /// 25 | /// 解法1 26 | /// 基本思路: 27 | /// 最直接的思路是再构建一个新的临时数组 28 | /// 先遍历一遍原数组,把其中的奇数依次添加到新数组中 29 | /// 再遍历一遍原数组把其中的偶数依次添加到新数组中 30 | /// 时间复杂度为O(2n) 31 | /// 32 | 33 | public int[] ReOrderArray(int[] array) 34 | { 35 | int[] temp = new int[array.Length]; 36 | int index = 0; 37 | for(int i = 0; i < array.Length; i ++){ 38 | if(array[i] % 2 != 0) 39 | temp[index ++] = array[i]; 40 | } 41 | for(int i = 0; i < array.Length; i ++){ 42 | if(array[i] % 2 == 0) 43 | temp[index ++] = array[i]; 44 | } 45 | return temp; 46 | } 47 | 48 | /// 49 | /// 解法2 50 | /// 基本思路: 51 | /// 解法1用到了临时数组,空间复杂度是O(n),某些情况下可能希望空间复杂度越低越好。 52 | /// 解法2虽然时间复杂度提高了,但降低了空间复杂度,不再需要额外的空间。 53 | /// 基本思路是遍历原数组,如果遇到了奇数元素,就将该元素向前移动,该元素前面的偶数元素都依次向后移动。 54 | /// 可以这样理解,每发现一个奇数时,就将这个奇数移动到了它最终应该在的位置上。 55 | /// 56 | 57 | public int[] ReOrderArray2(int[] array) 58 | { 59 | for(int i = 0; i < array.Length; i ++){ 60 | if(array[i] % 2 != 0){ 61 | int j = i, target = array[i]; 62 | while(j > 0 && array[j - 1] % 2 == 0){ 63 | array[j] = array[j - 1]; 64 | j --; 65 | } 66 | array[j] = target; 67 | } 68 | } 69 | return array; 70 | } 71 | 72 | public void Print(int[] array){ 73 | foreach(int item in array){ 74 | Console.WriteLine(item); 75 | } 76 | } 77 | 78 | public void Test() { 79 | 80 | int[] array = new int[]{1, 4, 3, 7, 2, 5, 6}; 81 | array = new int[]{4, 4, 4, 2, 5, 3, 6, 2}; 82 | array = new int[]{-4, 4, -4, 2, -5, 3, 6, 2, -1}; 83 | 84 | // Print(ReOrderArray(array)); 85 | Print(ReOrderArray2(array)); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /剑指offer/NumberOf1Between1AndN.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 整数中1出现的次数(从1到n整数中1出现的次数) 4 | 5 | 题目描述: 6 | 求出1~13的整数中1出现的次数,并算出100~1300的整数中1出现的次数? 7 | 为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。 8 | ACMer希望你们帮帮他,并把问题更加普遍化, 9 | 可以很快的求出任意非负整数区间中1出现的次数(从1 到 n 中1出现的次数)。 10 | 11 | 代码结构: 12 | class Solution 13 | { 14 | public int NumberOf1Between1AndN_Solution(int n) 15 | { 16 | // write code here 17 | } 18 | } 19 | */ 20 | using System; 21 | namespace NumberOf1Between1AndN { 22 | class Solution { 23 | 24 | /// 25 | /// 解法1 26 | /// 基本思路: 27 | /// 逐一考察1~n的每一个数里有多少个1。每个数字可以通过对10求余来判断最后一位是否为1 28 | /// 时间复杂度是n*ln(n) 29 | /// 30 | public int NumberOf1Between1AndN_Solution(int n) 31 | { 32 | int count = 0; 33 | for(int i = 1; i < n + 1; i ++){ 34 | int m = i; 35 | while(m > 0){ 36 | if(m % 10 == 1){ 37 | count ++; 38 | } 39 | m = m / 10; 40 | } 41 | } 42 | return count; 43 | } 44 | 45 | /// 46 | /// 解法2,数学归纳法 47 | /// 首先来看每个数字的个位是1的情况 48 | /// 以0-9为一个阶段会包含一个1,包含x个完整阶段就包含x个1。 49 | /// 对非完整阶段,比如6,显然如果这个数小于1就包含0个,大于等于1就包含1个 50 | /// 因此公式是:n / 10 * 1 + (n % 10) < 1 ? 0 : 1 51 | /// 再来看十位 52 | /// 以0-99为一个阶段,十位上会包含10个1(10 - 19) 53 | /// 对于非完整阶段,假设为m,如果m小于10就是0个,m大于等于10且小于19就是m - 10 + 1,m大于19就是10 54 | /// 再来看百位 55 | /// 以0-999为一个阶段,百位上会包含100个1(100 - 199) 56 | /// 对于非完整阶段,假设为m,如果m小于100就是0个,m大于等于100且小于199就是m - 100 + 1,m大于199就是100 57 | /// 千位,万位等等以此类推,总结公式就是 58 | /// 以i表示位数 59 | /// count(i) = n / (i * 10) * i + f(n % (i * 10)) 60 | /// f(mod) = if(mod < i) return 0 elseif mod < (2 * i -1) return mod - i + 1 else return i 61 | /// 对于f(mod)中的if else同样可以再进行归纳 62 | /// f(mod) = (0和mod - i + 1中的较大值)和i中的较小值 63 | /// 64 | public int NumberOf1Between1AndN_Solution2(int n) 65 | { 66 | int count = 0; 67 | for(int i = 1; i <= n; i = i * 10){ 68 | count += n / (i * 10) * i + Math.Min(Math.Max(0, n % (i * 10) - i + 1), i); 69 | } 70 | return count; 71 | } 72 | 73 | public void Test() { 74 | Console.WriteLine(NumberOf1Between1AndN_Solution(11)); 75 | Console.WriteLine(NumberOf1Between1AndN_Solution2(1)); 76 | } 77 | 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Leetcode/TwoSum.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 两数之和 4 | 5 | 题目描述: 6 | 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。 7 | 你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。 8 | 9 | 示例: 10 | 给定 nums = [2, 7, 11, 15], target = 9 11 | 因为 nums[0] + nums[1] = 2 + 7 = 9 12 | 所以返回 [0, 1] 13 | 14 | 代码结构: 15 | public class Solution { 16 | public int[] TwoSum(int[] nums, int target) { 17 | 18 | } 19 | } 20 | 21 | 题目链接: 22 | https://leetcode-cn.com/problems/two-sum/ 23 | */ 24 | using System; 25 | using System.Collections.Generic; 26 | namespace TwoSum { 27 | 28 | class Solution { 29 | 30 | /// 31 | /// 解法1,暴力法 32 | /// 基本思路: 33 | /// 直接遍历数组每个元素,查找和是target的两个元素的下标 34 | /// 35 | 36 | public int[] TwoSum(int[] nums, int target) { 37 | int[] ret = new int[2]; 38 | for(int i = 0; i < nums.Length; i ++){ 39 | ret[0] = i; 40 | for(int j = i + 1; j < nums.Length; j ++){ 41 | if(nums[j] + nums[ret[0]] == target){ 42 | ret[1] = j; 43 | return ret; 44 | } 45 | } 46 | } 47 | return null; 48 | } 49 | 50 | /// 51 | /// 解法2 52 | /// 基本思路: 53 | /// 空间换时间,利用hash表记录每个元素与下标之间的映射 54 | /// 遍历数组的过程中将已经遍历过的元素存入hash表中 55 | /// 后续的遍历再利用hash表中的记录来判断是否存在对应解 56 | /// 57 | 58 | public int[] TwoSum2(int[] nums, int target) { 59 | Dictionary dic = new Dictionary(); 60 | for(int i = 0; i < nums.Length; i ++){ 61 | int value = target - nums[i]; 62 | if(dic.ContainsKey(value)){ 63 | return new int[]{dic[value], i}; 64 | } 65 | dic[nums[i]] = i; 66 | } 67 | return null; 68 | } 69 | 70 | public void Print(int[] nums){ 71 | if(nums == null){ 72 | Console.WriteLine("null"); 73 | return; 74 | } 75 | Console.WriteLine(nums[0]); 76 | Console.WriteLine(nums[1]); 77 | } 78 | 79 | public void Test() { 80 | int[] nums = new int[]{2, 7, 11, 15}; 81 | // nums = new int[]{3, 2, 4}; 82 | // nums = new int[]{3, 3, 4}; 83 | 84 | int target = 9; 85 | // target = 22; 86 | // target = 66; 87 | // target = 6; 88 | 89 | // Print(TwoSum(nums, target)); 90 | Print(TwoSum2(nums, target)); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /剑指offer/DeleteDuplication.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 删除链表中重复的结点 4 | 5 | 题目描述: 6 | 在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 7 | 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5 8 | 9 | 代码结构: 10 | class Solution 11 | { 12 | public ListNode deleteDuplication(ListNode pHead) 13 | { 14 | // write code here 15 | } 16 | } 17 | */ 18 | using System; 19 | using System.Collections.Generic; 20 | namespace DeleteDuplication { 21 | 22 | public class ListNode 23 | { 24 | public int val; 25 | public ListNode next; 26 | public ListNode (int x) 27 | { 28 | val = x; 29 | } 30 | } 31 | 32 | class Solution { 33 | 34 | /// 35 | /// 解法 36 | /// 基本思路: 37 | /// 利用left指针指向确定不重复的元素 38 | /// 利用right指针查找重复的元素 39 | /// 如果right指针没有找到重复的元素,则两个指针同时右移一位,查找剩余的元素 40 | /// 如果有找到重复的元素,则将left的next直接指向right.next,以移除重复的元素 41 | /// 构造了一个head节点,为了方便处理头元素可能是重复元素需要移除的情况 42 | /// 43 | 44 | public ListNode DeleteDuplication(ListNode pHead) 45 | { 46 | ListNode head = new ListNode(0); 47 | head.next = pHead; 48 | ListNode left = head, right = head.next; 49 | while(right != null){ 50 | while(right.next != null && right.next.val == right.val){ 51 | right = right.next; 52 | } 53 | if(left.next == right) 54 | left = left.next; 55 | else 56 | left.next = right.next; 57 | right = right.next; 58 | } 59 | return head.next; 60 | } 61 | 62 | public void Print(ListNode node) { 63 | if (node == null){ 64 | Console.WriteLine("null"); 65 | return; 66 | } 67 | while(node != null) { 68 | Console.WriteLine(node.val); 69 | node = node.next; 70 | } 71 | } 72 | 73 | public void Test() { 74 | ListNode pHead = null; 75 | pHead = new ListNode(1); 76 | // pHead.next = new ListNode(1); 77 | pHead.next = new ListNode(3); 78 | pHead.next.next = new ListNode(3); 79 | pHead.next.next.next = new ListNode(3); 80 | pHead.next.next.next.next = new ListNode(4); 81 | pHead.next.next.next.next.next = new ListNode(4); 82 | pHead.next.next.next.next.next.next = new ListNode(5); 83 | 84 | Print(DeleteDuplication(pHead)); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /排序算法/MergeSort.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 算法名称: 3 | 归并排序 4 | 5 | 时间复杂度:O(n*log(2, n)) 6 | 7 | 空间复杂度:O(n),因为在实现过程中用到了一个临时序列来暂存归并过程中的中间结果 8 | 9 | 最好情况: 10 | O(n*log(2, n)) 11 | 12 | 最坏情况: 13 | O(n*log(2, n)) 14 | 15 | 稳定性:稳定 16 | 17 | 优点:稳定,若采用单链表作为存储结构,可实现就地排序,不需要额外空间 18 | 19 | 缺点:需要O(n)的额外空间 20 | */ 21 | using System; 22 | namespace MergeSort { 23 | 24 | class Solution { 25 | 26 | /// 27 | /// 基本思想: 28 | /// 所谓归并是指,把两个或两个以上的待排序列合并起来,形成一个新的有序序列。 29 | /// 2-路归并是指,将两个有序序列合并成为一个有序序列。 30 | /// 2-路归并排序的基本思想是,对于长度为n的无序序列来说, 31 | /// 归并排序把它看成是由n个只包括一个元素的有序序列组成,然后进行两两归并,最后形成包含n个元素的有序序列 32 | /// 即先将待排序列通过递归拆解成子序列,然后再对已经排好序的子序列进行合并 33 | /// 34 | 35 | public void MergeSort(int[] array){ 36 | MergeSortImpl(array, 0, array.Length - 1); 37 | } 38 | 39 | public void MergeSortImpl(int[] array, int left, int right){ 40 | if(left >= right) return; 41 | int middle = (left + right) / 2; 42 | MergeSortImpl(array, left, middle); 43 | MergeSortImpl(array, middle + 1, right); 44 | Merge(array, left, middle, right); 45 | } 46 | 47 | public void Merge(int[] array, int left, int middle, int right){ 48 | int[] temp = new int[right - left + 1]; 49 | int index = 0, lindex = left, rindex = middle + 1; 50 | while(lindex <= middle && rindex <= right){ 51 | if(array[rindex] < array[lindex]){ 52 | temp[index ++] = array[rindex ++]; 53 | }else{ 54 | temp[index ++] = array[lindex ++]; 55 | } 56 | } 57 | while(lindex <= middle){ 58 | temp[index ++] = array[lindex ++]; 59 | } 60 | while(rindex <= right){ 61 | temp[index ++] = array[rindex ++]; 62 | } 63 | 64 | while(--index >= 0){ 65 | array[left + index] = temp[index]; 66 | } 67 | 68 | } 69 | 70 | public void Test() { 71 | 72 | int[] array = new int[]{1, 4, 2, 3, 5}; 73 | // array = new int[]{0, 0, 0, 1, 1, 1, -1, -1, 0, -1}; 74 | // array = new int[]{4, 3, 3, 2, 1, 2, 3, 4, 3, 2, -1}; 75 | 76 | MergeSort(array); 77 | 78 | Print(array); 79 | } 80 | 81 | public void Print(int[] array){ 82 | foreach (int item in array) 83 | { 84 | Console.Write(item + " "); 85 | } 86 | Console.WriteLine(); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /剑指offer/FindGreatestSumOfSubArray.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 连续子数组的最大和 4 | 5 | 题目描述: 6 | HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。今天测试组开完会后,他又发话了: 7 | 在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。 8 | 但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢? 9 | 例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。 10 | 给一个数组,返回它的最大连续子序列的和,你会不会被他忽悠住?(子向量的长度至少是1) 11 | 12 | 代码结构: 13 | class Solution 14 | { 15 | public int FindGreatestSumOfSubArray(int[] array) 16 | { 17 | // write code here 18 | } 19 | } 20 | */ 21 | using System; 22 | namespace FindGreatestSumOfSubArray { 23 | class Solution { 24 | 25 | /// 26 | /// 解法1 27 | /// 基本思路: 28 | /// 依次遍历数组元素,是否吸收前面的元素作为连续子数组的前缀的标准是,它们的和是否为正数 29 | /// 例如当遍历到元素A时 30 | /// 判断元素A前面的元素和是否是正数,是正数,则加上A对最终的结果产生积极影响,保留前面的元素 31 | /// 否则丢弃前面的元素直接从A开始继续寻找最大和。 32 | /// 33 | public int FindGreatestSumOfSubArray(int[] array) 34 | { 35 | if(array == null || array.Length == 0){ 36 | return 0; 37 | } 38 | int max = array[0]; 39 | int sum = max; 40 | for(int i = 1; i < array.Length; i ++){ 41 | if(sum < 0){ 42 | sum = array[i]; 43 | }else{ 44 | sum += array[i]; 45 | } 46 | if(sum > max){ 47 | max = sum; 48 | } 49 | } 50 | return max; 51 | } 52 | 53 | /// 54 | /// 解法2,动态规划 55 | /// 基本思路: 56 | /// 可以这样理解,假设前n-1个连续元素最大和为x, 57 | /// 那前n个连续元素最大和就是x加最后一个元素值和最后一个元素值中的较大值 58 | /// F(n) = max(F(n -1) + array[n], array[n]),这里的array[n]表示数组末尾元素的值 59 | /// 注意这里的F(n),并不表示最终结果。最终结果应该是F(1),F(2)..F(n)中的较大值 60 | /// 61 | public int FindGreatestSumOfSubArray2(int[] array) 62 | { 63 | if(array == null || array.Length == 0){ 64 | return 0; 65 | } 66 | int f = array[0], m = f; 67 | for(int i = 1; i < array.Length; i ++){ 68 | f = (f + array[i]) > array[i] ? (f + array[i]) : array[i]; 69 | m = m > f ? m : f; 70 | } 71 | return m; 72 | } 73 | 74 | public void Test() { 75 | int[] array = new int[]{6,-3,-2,7,-15,1,2,6}; 76 | array = new int[]{-9, -8, 2, -1, 3}; 77 | // array = null; 78 | 79 | // Console.WriteLine(FindGreatestSumOfSubArray(array)); 80 | Console.WriteLine(FindGreatestSumOfSubArray2(array)); 81 | } 82 | 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /剑指offer/FindPath.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 二叉树中和为某一值的路径 4 | 5 | 题目描述: 6 | 输入一颗二叉树的根节点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。 7 | 路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。 8 | 9 | 代码结构: 10 | class Solution 11 | { 12 | public List> FindPath(TreeNode root, int expectNumber) 13 | { 14 | // write code here 15 | } 16 | } 17 | */ 18 | using System; 19 | using System.Collections.Generic; 20 | namespace FindPath { 21 | 22 | public class TreeNode 23 | { 24 | public int val; 25 | public TreeNode left; 26 | public TreeNode right; 27 | public TreeNode (int x) 28 | { 29 | val = x; 30 | } 31 | } 32 | 33 | class Solution { 34 | 35 | /// 36 | /// 解法,递归 37 | /// 基本思路: 38 | /// 通过定义成员变量list记录在递归过程中经过的每一个节点 39 | /// 当找到根节点时,且路径上的节点和值等于目标值时,则找到一条路径 40 | /// 算法的重点在于当递归经过一个节点将其加入到list中后 41 | /// 递归回溯以后再使用RemoveAt方法将其移除进行回退 42 | /// 43 | 44 | private List> pathList = new List>(); 45 | private List list = new List(); 46 | 47 | public List> FindPath(TreeNode root, int expectNumber) 48 | { 49 | if(root != null && root.val <= expectNumber){ 50 | list.Add(root.val); 51 | expectNumber -= root.val; 52 | if(expectNumber == 0 && root.left == null && root.right == null){ 53 | pathList.Add(new List(list)); 54 | } 55 | FindPath(root.left, expectNumber); 56 | FindPath(root.right, expectNumber); 57 | list.RemoveAt(list.Count - 1); 58 | } 59 | return pathList; 60 | } 61 | 62 | public void Print(List> lists){ 63 | if(lists == null){ 64 | Console.WriteLine("null"); 65 | return; 66 | } 67 | foreach (List list in lists) 68 | { 69 | foreach (int item in list) 70 | { 71 | Console.Write(item + " "); 72 | } 73 | Console.WriteLine(); 74 | } 75 | } 76 | 77 | public void Test() { 78 | 79 | TreeNode root = new TreeNode(1); 80 | root.left = new TreeNode(0); 81 | root.left.right = new TreeNode(1); 82 | root.left.left = new TreeNode(0); 83 | root.right = new TreeNode(1); 84 | root.right.right = new TreeNode(0); 85 | // root = null; 86 | 87 | int expectNumber = 2; 88 | 89 | Print(FindPath(root, expectNumber)); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /剑指offer/GetNext.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 二叉树的下一个结点 4 | 5 | 题目描述: 6 | 给定一个二叉树其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。 7 | 注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。 8 | 9 | 代码结构: 10 | class Solution 11 | { 12 | public TreeLinkNode GetNext(TreeLinkNode pNode) 13 | { 14 | // write code here 15 | } 16 | } 17 | 18 | 19 | */ 20 | using System; 21 | namespace GetNext { 22 | 23 | public class TreeLinkNode 24 | { 25 | public int val; 26 | public TreeLinkNode left; 27 | public TreeLinkNode right; 28 | public TreeLinkNode next; 29 | public TreeLinkNode (int x) 30 | { 31 | val = x; 32 | } 33 | } 34 | 35 | class Solution { 36 | 37 | /// 38 | /// 解法 39 | /// 基本思路: 40 | /// 中序遍历的顺序是先左节点,再根节点,再右节点 41 | /// 1. 节点为空,返回空 42 | /// 2. 如果节点有右节点,则一直向下寻找该右节点的左节点,直到叶子节点为止,即为下一个节点 43 | /// 3. 如果节点有父节点,且是父节点的左节点,则返回父节点。否则继续判断其父节点是否满足条件,直到父节点为空。 44 | /// 可以理解为中序遍历到一个子节点后,下一个节点一定是这个子节点所在左子树的父节点 45 | /// 前中后续遍历介绍 https://blog.csdn.net/FightLei/article/details/89281600 46 | /// 47 | 48 | public TreeLinkNode GetNext(TreeLinkNode pNode) 49 | { 50 | if(pNode == null){ 51 | return null; 52 | } 53 | 54 | if(pNode.right != null){ 55 | pNode = pNode.right; 56 | while(pNode.left != null){ 57 | pNode = pNode.left; 58 | } 59 | return pNode; 60 | } 61 | 62 | while(pNode.next != null){ 63 | if(pNode == pNode.next.left){ 64 | return pNode.next; 65 | } 66 | pNode = pNode.next; 67 | } 68 | 69 | return null; 70 | } 71 | 72 | public void Test() { 73 | TreeLinkNode pNode = null; 74 | pNode = new TreeLinkNode(0); 75 | pNode.left = new TreeLinkNode(1); 76 | pNode.left.next = pNode; 77 | 78 | pNode.left.right = new TreeLinkNode(3); 79 | pNode.left.right.next = pNode.left; 80 | 81 | 82 | pNode.right = new TreeLinkNode(2); 83 | pNode.right.next = pNode; 84 | 85 | pNode.right.left = new TreeLinkNode(4); 86 | pNode.right.left.next = pNode.right; 87 | 88 | TreeLinkNode node = GetNext(pNode); 89 | 90 | if (node == null){ 91 | Console.WriteLine("null"); 92 | }else{ 93 | Console.WriteLine(node.val); 94 | } 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /排序算法/QuickSort.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 算法名称: 3 | 快速排序 4 | 5 | 时间复杂度:O(n*log(2, n)) 6 | 7 | 空间复杂度:O(log(2, n)),递归深度,递归使用的栈空间,以2为底n的对数 8 | 9 | 最好情况: 10 | O(n*log(2, n)) 每次划分的结果都是左右两个分区大小相等时 11 | 12 | 最坏情况: 13 | O(n ^ 2) 当选择的分界点一直是最大元素或最小元素时,退化为冒泡排序 14 | 15 | 稳定性:不稳定,因为将元素移动到分界点两边时,会打乱原本相等元素的顺序 16 | 17 | 优点:极快 18 | 19 | 缺点:不稳定 20 | 21 | 优化: 22 | 当待排序列是部分有序时,固定选取枢轴使快排效率低下,要缓解这种情况,可以引入随机选取枢轴 23 | 当待排序列长度分割到一定大小后,使用插入排序,因为对于很小和部分有序的数组,直接插入排序效率更好 24 | 算法中调用两次QuickSortImpl进行递归,其实第二次可以使用循环代替,改为 left = pivot + 1 25 | */ 26 | using System; 27 | namespace QuickSort { 28 | 29 | class Solution { 30 | 31 | /// 32 | /// 基本思想: 33 | /// 每次从数组中选择一个目标元素(这里默认选择首元素)做为分界点 34 | /// 将数组中小于分界点的元素都移动到分界点左边 35 | /// 将数组中大于分界点的元素都移动到分界点右边 36 | /// 这样,每经过一趟排序,分界点都找到了它应该在的位置,然后以同样的方式递归处理左右两边的数组 37 | /// 直到每一个元素都做为分界点找到了自己的位置,即排序完成 38 | /// 39 | 40 | public void QuickSort(int[] array){ 41 | QuickSortImpl(array, 0, array.Length - 1); 42 | } 43 | 44 | public void QuickSortImpl(int[] array, int left, int right){ 45 | if(left >= right) return; 46 | int pivot = Partition(array, left, right); 47 | QuickSortImpl(array, left, pivot - 1); 48 | QuickSortImpl(array, pivot + 1, right); 49 | } 50 | 51 | public int Partition(int[] array, int left, int right){ 52 | int target = array[left]; 53 | while(left < right){ 54 | while(right > left && array[right] >= target){ 55 | right --; 56 | } 57 | if(right > left){ 58 | array[left] = array[right]; 59 | left ++; 60 | } 61 | while(left < right && array[left] <= target){ 62 | left ++; 63 | } 64 | if(left < right){ 65 | array[right] = array[left]; 66 | right --; 67 | } 68 | } 69 | array[left] = target; 70 | return left; 71 | } 72 | 73 | public void Test() { 74 | 75 | int[] array = new int[]{0, 3, 2, 4}; 76 | // array = new int[]{0, 0, 1, 0, 0}; 77 | // array = new int[]{-1, 6, 3, 2, 4, 0, 9}; 78 | 79 | QuickSort(array); 80 | 81 | Print(array); 82 | } 83 | 84 | public void Print(int[] array){ 85 | foreach (int item in array) 86 | { 87 | Console.Write(item + " "); 88 | } 89 | Console.WriteLine(); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /剑指offer/TreeDepth.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 二叉树的深度 4 | 5 | 题目描述: 6 | 输入一棵二叉树,求该树的深度。 7 | 从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。 8 | 9 | 代码结构: 10 | class Solution 11 | { 12 | public int TreeDepth(TreeNode pRoot) 13 | { 14 | // write code here 15 | } 16 | } 17 | */ 18 | using System; 19 | using System.Collections.Generic; 20 | namespace TreeDepth { 21 | 22 | public class TreeNode 23 | { 24 | public int val; 25 | public TreeNode left; 26 | public TreeNode right; 27 | public TreeNode (int x) 28 | { 29 | val = x; 30 | } 31 | } 32 | 33 | class Solution { 34 | 35 | /// 36 | /// 解法1,递归 37 | /// 基本思路: 38 | /// 递归遍历二叉树的左右子节点,树的深度等于其左右子树深度中的最大值加1 39 | /// 40 | 41 | public int TreeDepth(TreeNode pRoot) 42 | { 43 | if(pRoot == null){ 44 | return 0; 45 | } 46 | int left = TreeDepth(pRoot.left) + 1; 47 | int right = TreeDepth(pRoot.right) + 1; 48 | return left > right ? left : right; 49 | } 50 | 51 | /// 52 | /// 解法2,非递归,层次遍历 53 | /// 基本思路: 54 | /// 利用一个辅助队列,队列中依次保存二叉树每一层的所有节点。保存了多少次,就是该二叉树的深度。 55 | /// 每次都是将队列中上一层的所有节点弹出,替换为他们的左右子节点 56 | /// 57 | 58 | public int TreeDepth2(TreeNode pRoot){ 59 | if(pRoot == null){ 60 | return 0; 61 | } 62 | Queue queue = new Queue(); 63 | queue.Enqueue(pRoot); 64 | int depth = 0; 65 | while(queue.Count > 0){ 66 | int count = queue.Count; 67 | for(int i = 0; i < count; i ++){ 68 | TreeNode cur = queue.Dequeue(); 69 | if (cur.left != null){ 70 | queue.Enqueue(cur.left); 71 | } 72 | if (cur.right != null){ 73 | queue.Enqueue(cur.right); 74 | } 75 | } 76 | depth ++; 77 | } 78 | return depth; 79 | } 80 | 81 | public void Test() { 82 | TreeNode node = new TreeNode(1); 83 | // node = null; 84 | node.left = new TreeNode(2); 85 | node.left.left = new TreeNode(3); 86 | node.right = new TreeNode(4); 87 | // node.right.right = new TreeNode(5); 88 | // node.right.right.right = new TreeNode(6); 89 | 90 | // Console.WriteLine(TreeDepth(node)); 91 | Console.WriteLine(TreeDepth2(node)); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /Leetcode/ThreeSum.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 三数之和 4 | 5 | 题目描述: 6 | 给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。 7 | 注意:答案中不可以包含重复的三元组。 8 | 9 | 示例: 10 | 给定数组 nums = [-1, 0, 1, 2, -1, -4], 11 | 满足要求的三元组集合为: 12 | [ 13 | [-1, 0, 1], 14 | [-1, -1, 2] 15 | ] 16 | 17 | 代码结构: 18 | public class Solution { 19 | public IList> ThreeSum(int[] nums) { 20 | 21 | } 22 | } 23 | 24 | 题目链接: 25 | https://leetcode-cn.com/problems/3sum/ 26 | 27 | 官方题解: 28 | https://leetcode-cn.com/problems/3sum/solution/san-shu-zhi-he-by-leetcode-solution/ 29 | */ 30 | using System; 31 | using System.Collections.Generic; 32 | namespace ThreeSum { 33 | 34 | class Solution { 35 | 36 | /// 37 | /// 解法1 38 | /// 基本思路: 39 | /// 题目要求是不可重复的三元组,例如为了避免重复的 a + b + c = 0 和 b + c + a = 0 40 | /// 只要要求a <= b <= c,即可,所以首先对数组进行排序, 41 | /// 按从小到大的顺序,三重循环枚举所有的三元组 42 | /// 注意排序无法解决重复元素的情况,还需要额外处理,每次循环如果当前元素与上次元素相同,则可以直接跳过 43 | /// 当第一层循环确定a时 44 | /// 第二层循环确定b时,当第三层循环确定c后,满足a + b + c = 0,找到一个三元组 45 | /// 继续第二层循环,向右找,此时的b'一定大于b(因为是从小到大的顺序) 46 | /// 那么根据b' > b,想要满足a + b' + c' = 0,则c'必须小于c,即c'一定在c的左边 47 | /// 所以后两层循环可以变成双指针遍历,b是从左到右查找,c是从右到左查找,从而将时间复杂度由O(n^2)降为O(n) 48 | /// 49 | 50 | public IList> ThreeSum(int[] nums) { 51 | IList> lists = new List>(); 52 | Array.Sort(nums); 53 | for(int i = 0; i < nums.Length; i ++){ 54 | if(i > 0 && nums[i] == nums[i - 1]) continue; 55 | int k = nums.Length - 1; 56 | for(int j = i + 1; j < nums.Length; j ++){ 57 | if(j > i + 1 && nums[j] == nums[j - 1]) continue; 58 | while(k > j && nums[j] + nums[k] + nums[i] > 0) 59 | k --; 60 | if(k == j) break; 61 | if(nums[j] + nums[k] + nums[i] == 0) 62 | lists.Add(new List(){nums[i], nums[j], nums[k]}); 63 | } 64 | } 65 | return lists; 66 | } 67 | 68 | public void Print(IList> lists){ 69 | foreach(IList list in lists){ 70 | foreach(int i in list){ 71 | Console.Write(i + " "); 72 | } 73 | Console.WriteLine(); 74 | } 75 | } 76 | 77 | public void Test() { 78 | int[] nums = new int[]{-1, 0, 1, 2, -1, -4}; 79 | // nums = new int[]{}; 80 | // nums = new int[]{0, 0}; 81 | // nums = new int[]{0, 0 , 0, 0}; 82 | // nums = new int[]{2, 1 , -1, 0, -2, -1}; 83 | 84 | Print(ThreeSum(nums)); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /剑指offer/HasPath.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 矩阵中的路径 4 | 5 | 题目描述: 6 | 请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。 7 | 路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。 8 | 如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。 9 | 例如 a b c e s f c s a d e e 矩阵中包含一条字符串"bcced"的路径, 10 | 但是矩阵中不包含"abcb"路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后, 11 | 路径不能再次进入该格子。 12 | 13 | 代码结构: 14 | class Solution 15 | { 16 | public bool hasPath(string matrix, int rows, int cols, string path) 17 | { 18 | // write code here 19 | } 20 | } 21 | */ 22 | using System; 23 | namespace HasPath { 24 | 25 | class Solution { 26 | 27 | /// 28 | /// 解法 29 | /// 基本思路: 30 | /// 将matrix字符串转换为多维数组,分别以数组的每个元素为起点,判断路径是否存在 31 | /// 从起点开始按上下左右的顺序搜索,已经经过的元素标记为-1 32 | /// 如果此路不通,则回溯到上一层,并还原标记位 33 | /// 34 | 35 | public bool HasPathImpl(int[,] arr, int row, int col, string path, int index) { 36 | if(index >= path.Length){ 37 | return true; 38 | } 39 | if(row < 0 || row >= arr.GetLength(0) || col < 0 || col >= arr.GetLength(1)){ 40 | return false; 41 | } 42 | if(arr[row, col] == path[index]){ 43 | int temp = arr[row, col]; 44 | arr[row, col] = -1; 45 | bool ret = HasPathImpl(arr, row, col + 1, path, index + 1) || HasPathImpl(arr, row, col - 1, path, index + 1) 46 | || HasPathImpl(arr, row + 1, col, path, index + 1) || HasPathImpl(arr, row - 1, col, path, index + 1); 47 | arr[row, col] = temp; 48 | return ret; 49 | } 50 | return false; 51 | } 52 | 53 | public bool HasPath(string matrix, int rows, int cols, string path) 54 | { 55 | int[,] arr = new int[rows, cols]; 56 | for (int i = 0; i < rows; i++) 57 | { 58 | for (int j = 0; j < cols; j++) 59 | { 60 | arr[i, j] = matrix[i * cols + j]; 61 | } 62 | } 63 | 64 | for (int i = 0; i < rows; i++) 65 | { 66 | for (int j = 0; j < cols; j++) 67 | { 68 | bool ret = HasPathImpl(arr, i, j, path, 0); 69 | if(ret){ 70 | return true; 71 | } 72 | } 73 | } 74 | return false; 75 | } 76 | 77 | public void Test() { 78 | 79 | string matrix = "abcesfcsadee"; 80 | int rows = 3; 81 | int cols = 4; 82 | string path = "bcced"; 83 | // path = "abcb"; 84 | // path = "bfb"; 85 | 86 | Console.WriteLine(HasPath(matrix, rows, cols, path)); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /剑指offer/FirstAppearingOnce.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 字符流中第一个不重复的字符 4 | 5 | 题目描述: 6 | 请实现一个函数用来找出字符流中第一个只出现一次的字符。 7 | 例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。 8 | 当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。 9 | 10 | 输出描述: 11 | 如果当前字符流没有存在出现一次的字符,返回#字符。 12 | 13 | 代码结构: 14 | class Solution 15 | { 16 | public char FirstAppearingOnce() 17 | { 18 | // write code here 19 | } 20 | 21 | public void Insert(char c) 22 | { 23 | // write code here 24 | } 25 | } 26 | */ 27 | using System; 28 | using System.Collections.Generic; 29 | namespace FirstAppearingOnce { 30 | 31 | class Solution { 32 | 33 | /// 34 | /// 解法1 35 | /// 基本思路: 36 | /// 根据ASCII码一共定义了128个字符,因此使用长度为128的数组记录每个字符的出现字数 37 | /// 当每个字符出现1次时,入队列。查找第一个不重复字符时,将出现次数不为1的字符,出队。 38 | /// 这样队首的元素就是第一个不重复的字符 39 | /// 40 | 41 | int[] array = new int[128]; 42 | Queue queue = new Queue(); 43 | 44 | public char FirstAppearingOnce() 45 | { 46 | while(queue.Count > 0 && array[queue.Peek()] != 1){ 47 | queue.Dequeue(); 48 | } 49 | return queue.Count == 0 ? '#' : queue.Peek(); 50 | } 51 | 52 | public void Insert(char c) 53 | { 54 | array[c] ++; 55 | if(array[c] == 1){ 56 | queue.Enqueue(c); 57 | } 58 | } 59 | 60 | 61 | /// 62 | /// 解法2 63 | /// 基本思路: 64 | /// 使用长度为128的数组记录只出现1次的字符的出现顺序,出现大于1次的字符对应的值都为-1,未出现的对应为0 65 | /// 遍历数组,找到数组中元素值最小且大于0的,就是第一个不重复的字符 66 | /// 67 | 68 | int[] array2 = new int[128]; 69 | int index = 0; 70 | public char FirstAppearingOnce2() 71 | { 72 | int min = int.MaxValue; 73 | char c = '#'; 74 | for(int i = 0; i < array2.Length; i ++){ 75 | if(array2[i] > 0 && array2[i] < min){ 76 | min = array2[i]; 77 | c = (char)i; 78 | } 79 | } 80 | return c; 81 | } 82 | 83 | public void Insert2(char c) 84 | { 85 | if(array2[c] == 0){ 86 | array2[c] = ++index; 87 | }else{ 88 | array2[c] = -1; 89 | } 90 | } 91 | 92 | public void Test() { 93 | // Insert('.'); 94 | Insert('g'); 95 | Insert('o'); 96 | // Insert('o'); 97 | // Insert('g'); 98 | // Insert('l'); 99 | // Insert('e'); 100 | 101 | Console.WriteLine(FirstAppearingOnce()); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /剑指offer/VerifySquenceOfBST.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 二叉搜索树的后序遍历序列 4 | 5 | 题目描述: 6 | 输入一个非空整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。 7 | 如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。 8 | 9 | 代码结构: 10 | class Solution 11 | { 12 | public bool VerifySquenceOfBST(int[] sequence) 13 | { 14 | // write code here 15 | } 16 | } 17 | 18 | 补充: 19 | 二叉搜索树(Binary Search Tree)定义: 20 | 1. 可以是空树 21 | 2. 若不是空树 22 | 若它的左子树不空,则左子树所有节点的值均小于它的根节点的值 23 | 若它的右子树不空,则右子树所有节点的值均大于它的根节点的值 24 | 它的左,右子树也分别为二叉搜索树 25 | */ 26 | using System; 27 | namespace VerifySquenceOfBST { 28 | 29 | class Solution { 30 | 31 | /// 32 | /// 解法1,递归 33 | /// 基本思路: 34 | /// 对于一个二叉搜索树的后序序列,最后一个元素一定是根 35 | /// 如果去掉最后一个元素,即去掉根元素,剩下的序列应该满足 36 | /// 前一段(左子树)都小于根元素 37 | /// 后一段(右子树)都大于根元素 38 | /// 然后这两段(子树)也必须是合法的二叉搜索树后序序列。使用递归处理。 39 | /// 40 | 41 | public bool VerifySquenceOfBST(int[] sequence) 42 | { 43 | if(sequence == null || sequence.Length == 0) return false; 44 | return VerifySquenceOfBSTImpl(sequence, 0, sequence.Length - 1); 45 | } 46 | 47 | public bool VerifySquenceOfBSTImpl(int[] sequence, int left, int right){ 48 | if(left >= right) return true; 49 | int index = right - 1; 50 | for(int i = left; i < right; i ++){ 51 | if(sequence[i] > sequence[right]) 52 | index = i; 53 | else if(index != right - 1) 54 | return false; 55 | } 56 | return VerifySquenceOfBSTImpl(sequence, left, index) && VerifySquenceOfBSTImpl(sequence, index, right - 1); 57 | } 58 | 59 | /// 60 | /// 解法2,非递归 61 | /// 基本思路: 62 | /// 采用非递归形式,但其实仍然是递归思想 63 | /// 与解法1将去掉根元素的序列看成是左右两个子树不同的是 64 | /// 非递归算法是将剩下的序列看成是一棵子树,即右子树。 65 | /// 原来的左子树被看成是右子树的左子树 66 | /// 根据二叉搜索树的性质,右子树的所有元素一定是大于左子树的所有元素的,所以这种转换是成立的 67 | /// 68 | 69 | public bool VerifySquenceOfBST2(int[] sequence) 70 | { 71 | if(sequence == null || sequence.Length == 0) return false; 72 | int len = sequence.Length, i = 0; 73 | while(--len > 0){ 74 | while(sequence[i] < sequence[len]) i ++; 75 | while(sequence[i] > sequence[len]) i ++; 76 | if(i < len) return false; 77 | i = 0; 78 | } 79 | return true; 80 | } 81 | 82 | public void Test() { 83 | 84 | int[] sequence = new int[]{1, 2, 3, 4, 5}; 85 | sequence = new int[]{5, 4, 3, 2, 1}; 86 | // sequence = new int[]{1, 6, 5, 9, 13, 12, 8}; 87 | // sequence = new int[]{1, 6, 5, 9, 11, 7, 8}; 88 | // sequence = new int[]{}; 89 | 90 | // Console.WriteLine(VerifySquenceOfBST(sequence)); 91 | Console.WriteLine(VerifySquenceOfBST2(sequence)); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /剑指offer/InversePairs.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 数组中的逆序对 4 | 5 | 题目描述: 6 | 在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。 7 | 输入一个数组,求出这个数组中的逆序对的总数P。 8 | 并将P对1000000007取模的结果输出。 即输出P%1000000007 9 | 10 | 输入描述: 11 | 题目保证输入的数组中没有的相同的数字 12 | 数据范围: 13 | 对于%50的数据,size<=10^4 14 | 对于%75的数据,size<=10^5 15 | 对于%100的数据,size<=2*10^5 16 | 17 | 代码结构: 18 | class Solution 19 | { 20 | public int InversePairs(int[] data) 21 | { 22 | // write code here 23 | } 24 | } 25 | */ 26 | using System; 27 | namespace InversePairs { 28 | class Solution { 29 | 30 | /// 31 | /// 解法,归并排序思想 32 | /// 基本思路: 33 | /// 将含有n个元素的数组,不断的进行二分为两个子数组。直到每个子数组都只含有一个元素。 34 | /// 再进行合并两个子数组,利用一个临时数组,不断从两个子数组中选出最大值放到临时数组中。 35 | /// 在比较时,由于每个子数组都是有序递减的,所以只需要比较子数组的首元素即可(此时进行逆序对的数量统计,如果 36 | /// 该元素已经大于右边子数组的首元素,则右边子数组的所有元素都小于该元素,即都可以和该元素构成逆序对)。 37 | /// 归并排序介绍 https://www.cnblogs.com/iwiniwin/p/12609549.html 38 | /// 39 | 40 | public int Merge(int[] data, int start, int mid, int end){ 41 | int count = 0; 42 | int i = start; 43 | int j = mid + 1; 44 | int k = 0; 45 | int[] temp = new int[end - start + 1]; 46 | while(i <= mid && j <= end){ 47 | if(data[i] > data[j]){ 48 | count += (end - j + 1); 49 | if(count > 1000000007){ 50 | count %= 1000000007; 51 | } 52 | temp[k ++] = data[i ++]; 53 | }else{ 54 | temp[k ++] = data[j ++]; 55 | } 56 | } 57 | while(i <= mid){ 58 | temp[k ++] = data[i ++]; 59 | } 60 | while(j <= end){ 61 | temp[k ++] = data[j ++]; 62 | } 63 | for(i = 0; i < k; i ++){ 64 | data[start + i] = temp[i]; 65 | } 66 | return count; 67 | } 68 | 69 | public int MergeSort(int[] data, int start, int end){ 70 | if(start >= end){ 71 | return 0; 72 | } 73 | int mid = (start + end) / 2; 74 | return (MergeSort(data, start, mid) + MergeSort(data, mid + 1, end) + Merge(data, start, mid, end)) % 1000000007; 75 | } 76 | 77 | public int InversePairs(int[] data) 78 | { 79 | if(data == null){ 80 | return 0; 81 | } 82 | return MergeSort(data, 0, data.Length - 1); 83 | } 84 | 85 | public void Test() { 86 | int[] data = new int[]{1, 6, 2, 8, 4, 1, 0, 7}; 87 | // data = new int[]{1, 2, 3, 4, 5, 6, 7, 0}; 88 | // data = null; 89 | // data = new int[]{}; 90 | // data = new int[]{1, 1, 0}; 91 | 92 | Console.WriteLine(InversePairs(data)); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /剑指offer/JumpFloorII.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 变态跳台阶 4 | 5 | 题目描述: 6 | 一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。 7 | 8 | 代码结构: 9 | class Solution 10 | { 11 | public int jumpFloorII(int number) 12 | { 13 | // write code here 14 | } 15 | } 16 | 17 | 想法: 18 | 当拿到一道题时,不知道怎么下手去解题,毫无头绪时,试着找找规律,可能会有惊喜 19 | */ 20 | using System; 21 | namespace JumpFloorII { 22 | 23 | class Solution { 24 | 25 | /// 26 | /// 解法1 27 | /// 基本思路: 28 | /// 列出结果的前几项,找找规律,可能会有意想不到的收获 29 | /// 对于1级台阶,青蛙只有1 = 2^0种跳法 30 | /// 对于2级台阶,青蛙有2 = 2^1种跳法(分别是{1,1}, {2}) 31 | /// 对于3级台阶,青蛙有4 = 2^2种跳法(分别是{1,1,1}, {1,2}, {2,1}, {3}) 32 | /// 对于4级台阶,青蛙有8 = 2^3种跳法(分别是{1,1,1,1}, {1,1,2}, {1,2,1}, {2,1,1}, {2,2}, {1,3}, {3,1}, {4}) 33 | /// 不难发现,对于n级台阶,总跳法数是2^(n-1) 34 | /// 最终这道题被转换成如何求解2^(n-1),可以使用整数的快速幂解题 35 | /// 详细介绍 https://www.cnblogs.com/iwiniwin/p/10807310.html 36 | /// 37 | 38 | public int JumpFloorII(int number){ 39 | if(number <= 0) return 0; 40 | int n = number - 1; 41 | int m = 2; 42 | int ret = 1; 43 | while(n > 0){ 44 | if((n & 1) > 0){ 45 | ret *= m; 46 | } 47 | m *= m; 48 | n >>= 1; 49 | } 50 | return ret; 51 | } 52 | 53 | /// 54 | /// 解法2 55 | /// 基本思路: 56 | /// 对于求解2^(n-1),可以继续优化,利用左移运算,只使用一行代码就可以得到2^(n-1) 57 | /// 对于数字1,左移1位二进制表示是10,即2 = 2^1 58 | /// 左移2位是100,即4 = 2^2 59 | /// 左移3位是1000,即8 = 2^3 60 | /// 以此类推,左移n位,就是2^n 61 | /// 62 | 63 | public int JumpFloorII2(int number) 64 | { 65 | return number > 0 ? 1 << (number - 1) : 0; 66 | } 67 | 68 | /// 69 | /// 解法3,递归 70 | /// 基本思路: 71 | /// 先来看F(n),对于一个n级台阶来说 72 | /// 青蛙第一次可以跳1级,则还剩n - 1级台阶,即F(n - 1) 73 | /// 青蛙第一次可以跳2级,则还剩n - 2级台阶,即F(n - 2) 74 | /// ... 75 | /// 青蛙第一次可以跳n - 1级,则还剩1级台阶,即F(1) 76 | /// 青蛙第一次可以跳n级,即1种跳法 77 | /// 则F(n) = F(n - 1) + F(n - 2) + F(n - 3) + F(n - 4) + ... + F(1) + 1 78 | /// 很显然F(1)= 1,在已知F(1)的情况下,我们可以利用递归解这道题 79 | /// 80 | 81 | public int JumpFloorII3(int number) 82 | { 83 | int count = number > 0 ? 1 : 0; 84 | for(int i = number - 1; i > 0; i --){ 85 | count += JumpFloorII3(i); 86 | } 87 | return count; 88 | } 89 | 90 | public void Test() { 91 | 92 | int number = 3; 93 | number = 5; 94 | // number = 1; 95 | // number = 0; 96 | 97 | // Console.WriteLine(JumpFloorII(number)); 98 | Console.WriteLine(JumpFloorII2(number)); 99 | // Console.WriteLine(JumpFloorII3(number)); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /剑指offer/MinNumberInRotateArray.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 旋转数组的最小数字 4 | 5 | 题目描述: 6 | 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 7 | 输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。 8 | 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。 9 | NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。 10 | 11 | 代码结构: 12 | class Solution 13 | { 14 | public int minNumberInRotateArray(int[] rotateArray) 15 | { 16 | // write code here 17 | } 18 | } 19 | */ 20 | using System; 21 | namespace MinNumberInRotateArray { 22 | 23 | class Solution { 24 | 25 | /// 26 | /// 解法1 27 | /// 基本思路: 28 | /// 对于非减数组来说,数组左边的元素一定小于等于数组右边的元素。 29 | /// 当对非减数组进行旋转后(把数组最开始的元素搬到末尾), 30 | /// 则在遍历过程中可能会出现左边的元素反而小于右边的元素,当第一次出现这种情况时, 31 | /// 一定是原非减数组的开头,即整个数组的最小元素。 32 | /// 33 | 34 | public int MinNumberInRotateArray(int[] rotateArray) 35 | { 36 | for(int i = 0; i < rotateArray.Length - 1; i ++){ 37 | if(rotateArray[i] > rotateArray[i + 1]){ 38 | return rotateArray[i + 1]; 39 | } 40 | } 41 | return rotateArray.Length == 0 ? 0 : rotateArray[rotateArray.Length - 1]; 42 | } 43 | 44 | /// 45 | /// 解法2 46 | /// 基本思路: 47 | /// 对于有序数组的查找问题,首先想到的就是二分查找。 48 | /// 本题是非递减数组的旋转数组,仍具有一定的顺序性,所以稍微修改一下二分查找仍然可以解题 49 | /// 旋转数组可以看成是由左右两个递增数组组成 50 | /// 通过将right指向的元素与middle指向的元素比较 51 | /// 如果小于,则说明middle处于左边的递增数组,left = middle + 1,搜寻范围右移 52 | /// 如果大于,则说明middle处于右边的递增数组,right = middle,搜寻范围左移 53 | /// 如果等于,此时并不能判断到底处于左边还是右边,可以看成是顺序性丢失了,此时right -- ,二分查找退化为普通顺序遍历 54 | /// 二分查找介绍 https://www.cnblogs.com/iwiniwin/p/10793650.html 55 | /// 56 | 57 | public int MinNumberInRotateArray2(int[] rotateArray){ 58 | if(rotateArray.Length == 0) return 0; 59 | int left = 0, right = rotateArray.Length - 1; 60 | while(left < right){ 61 | int middle = (right + left) / 2; 62 | if(rotateArray[right] < rotateArray[middle]){ 63 | left = middle + 1; 64 | }else if(rotateArray[right] > rotateArray[middle]){ 65 | right = middle; 66 | }else{ 67 | right --; 68 | } 69 | } 70 | return rotateArray[left]; 71 | } 72 | 73 | public void Test() { 74 | 75 | int[] rotateArray = new int[]{1, 2, 3, 4, 0}; 76 | rotateArray = new int[]{}; 77 | rotateArray = new int[]{3, 4, 5, 5, 5, 6, 6, 1, 1, 2}; 78 | // rotateArray = new int[]{3, 4, 5, 5, 2, 2}; 79 | // rotateArray = new int[]{1, 2, 1}; 80 | // rotateArray = new int[]{1, 0}; 81 | // rotateArray = new int[]{1}; 82 | // rotateArray = new int[]{10,1,10,10,10}; 83 | rotateArray = new int[]{10,10,10,1,10,10}; 84 | 85 | // Console.WriteLine(MinNumberInRotateArray(rotateArray)); 86 | Console.WriteLine(MinNumberInRotateArray2(rotateArray)); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /排序算法/HeapSort.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 算法名称: 3 | 堆排序 4 | 5 | 时间复杂度:O(n*log(2, n)) 6 | 7 | 空间复杂度:O(1) 8 | 9 | 最好情况: 10 | O(n*log(2, n)) 11 | 12 | 最坏情况: 13 | O(n*log(2, n)),它的最坏情况接近于平均性能 14 | 15 | 稳定性:不稳定 16 | 17 | 优点:在最坏情况下性能优于快速排序。由于在直接选择排序的基础上利用了比较结果形成堆。效率提高很大。 18 | 19 | 缺点:不稳定,初始建堆所需比较次数较多,因此记录数较少时不宜采用 20 | */ 21 | using System; 22 | namespace HeapSort { 23 | 24 | class Solution { 25 | 26 | /// 27 | /// 基本思想: 28 | /// 对一组待排序列的元素,首先将它们按照堆的定义排成一个序列,常称为建堆,从而输出堆顶的最大或最小元素。 29 | /// 然后对剩余的元素再建堆,常称为重新调整成堆,即可得到次大(次小)元素,如此反复进行,直到全部元素排成有序序列为止。 30 | /// 31 | /// 如何建堆:首先将待排序列画成一颗完全二叉树,然后把得到的完全二叉树再转换成堆。 32 | /// 从最后一个分支节点开始(n / 2 - 1的节点),依次将所有以分支节点为根的二叉树调整成堆, 33 | /// 即先将子树构建成堆,再将父树构建成堆。 34 | /// 当这个过程持续到根节点时,整个二叉树就调整成了堆,即建堆完成。 35 | /// 36 | /// 如何调整堆:假设被调整的分支节点为A,它的左孩子为B,右孩子为C。 37 | /// 那么当A开始进行堆调整时,根据上面建堆的方式,以B和以C为根的二叉树都已经是堆了。 38 | /// 如果节点A的值大于B和C的值(以大顶堆为例),那么以A为根的二叉树已经是堆。 39 | /// 如果A节点的值小于B节点或C节点的值,那么节点A与值最大的那个孩子节点变换位置。 40 | /// 此时需要将A继续与和它交换的那个孩子的原来的两个孩子进行比较, 41 | /// 以此类推,直到节点A向下渗透到适当的位置为止。 42 | /// 43 | /// 如果要从小到大排序,则使用大顶堆,如果要从大到小排序,则使用小顶堆。 44 | /// 原因是堆顶元素需要交换到序列尾部 45 | /// 46 | 47 | public void HeapSort(int[] array){ 48 | // 建堆 49 | for(int i = array.Length / 2 - 1; i >= 0; i --){ 50 | BuildHeap(array, i, array.Length - 1); 51 | } 52 | // 调整堆 53 | for(int i = array.Length - 1; i > 0; i --){ 54 | Swap(array, 0, i); 55 | BuildHeap(array, 0, i - 1); 56 | } 57 | } 58 | 59 | public void BuildHeap(int[] array, int left, int right){ 60 | int target = array[left]; 61 | for(int i = 2 * left + 1; i <= right; i = 2 * i + 1){ 62 | if(i < right && array[i + 1] > array[i]){ 63 | i ++; 64 | } 65 | if(target >= array[i]){ 66 | break; 67 | } 68 | array[left] = array[i]; 69 | left = i; 70 | } 71 | array[left] = target; 72 | } 73 | 74 | public void Swap(int[] array, int i, int j){ 75 | int temp = array[i]; 76 | array[i] = array[j]; 77 | array[j] = temp; 78 | } 79 | 80 | public void Test() { 81 | 82 | int[] array = new int[]{3, 4, 2, 1}; 83 | array = new int[]{0, 0, 0, 1, 1, 1, -1, -1, 0, -1}; 84 | // array = new int[]{1, 2}; 85 | // array = new int[]{2, 1}; 86 | // array = new int[]{3}; 87 | 88 | HeapSort(array); 89 | 90 | Print(array); 91 | } 92 | 93 | public void Print(int[] array){ 94 | foreach (int item in array) 95 | { 96 | Console.Write(item + " "); 97 | } 98 | Console.WriteLine(); 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Leetcode/RemoveDuplicates.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 删除排序数组中的重复项 4 | 5 | 题目描述: 6 | 给定一个排序数组,你需要在 原地 删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。 7 | 不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。 8 | 9 | 示例 1: 10 | 给定数组 nums = [1,1,2], 11 | 函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。 12 | 你不需要考虑数组中超出新长度后面的元素。 13 | 14 | 示例 2: 15 | 给定 nums = [0,0,1,1,1,2,2,3,3,4], 16 | 函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。 17 | 你不需要考虑数组中超出新长度后面的元素。 18 | 19 | 说明: 20 | 为什么返回数值是整数,但输出的答案是数组呢? 21 | 请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。 22 | 你可以想象内部操作如下: 23 | // nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝 24 | int len = removeDuplicates(nums); 25 | // 在函数里修改输入数组对于调用者是可见的。 26 | // 根据你的函数返回的长度, 它会打印出数组中该长度范围内的所有元素。 27 | for (int i = 0; i < len; i++) { 28 | print(nums[i]); 29 | } 30 | 31 | 代码结构: 32 | public class Solution { 33 | public int RemoveDuplicates(int[] nums) { 34 | 35 | } 36 | } 37 | 38 | 题目链接: 39 | https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array/ 40 | */ 41 | using System; 42 | using System.Collections.Generic; 43 | namespace RemoveDuplicates { 44 | 45 | class Solution { 46 | 47 | /// 48 | /// 解法1 49 | /// 基本思路: 50 | /// 依次枚举每个元素,当出现重复元素时,将此重复元素移动到数组末尾,后面依次向前移动一位 51 | /// 52 | 53 | public int RemoveDuplicates(int[] nums) { 54 | int index = 1, len = nums.Length; 55 | while(index < len){ 56 | if(nums[index] == nums[index - 1]){ 57 | for(int i = index + 1; i < len; i ++){ 58 | nums[i - 1] = nums[i]; 59 | } 60 | nums[-- len] = nums[index]; 61 | }else{ 62 | index ++; 63 | } 64 | } 65 | return len; 66 | } 67 | 68 | /// 69 | /// 解法2,双指针 70 | /// 基本思路: 71 | /// 定义i慢指针,j快指针 72 | /// 如果nums[i] == nums[j]则一直 ++ j,通过j跳过重复项 73 | /// 直到nums[i] != nums[j]的时候,就可以把 i + 1然后,把nums[j]赋值给nums[i] 74 | /// i可以理解为一直指向的是找到的最后一个未重复元素 75 | /// 76 | /// 77 | /// 78 | 79 | public int RemoveDuplicates2(int[] nums) { 80 | if(nums == null || nums.Length <= 0) return 0; 81 | int i = 0, j = 0; 82 | while(++ j < nums.Length){ 83 | if(nums[i] != nums[j]){ 84 | nums[++ i] = nums[j]; 85 | } 86 | } 87 | return i + 1; 88 | } 89 | 90 | public void Print(int[] nums, int len){ 91 | Console.WriteLine(len); 92 | for(int i = 0; i < len; i ++){ 93 | Console.Write(nums[i] + " "); 94 | } 95 | Console.WriteLine(); 96 | } 97 | 98 | public void Test() { 99 | int[] nums = new int[]{1, 1, 2}; 100 | // nums = new int[]{0, 0, 1, 1, 1, 2, 2, 3, 3, 4}; 101 | // nums = new int[]{2}; 102 | // nums = new int[]{}; 103 | 104 | // Print(nums, RemoveDuplicates(nums)); 105 | Print(nums, RemoveDuplicates2(nums)); 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /剑指offer/StrToInt.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 把字符串转换成整数 4 | 5 | 题目描述: 6 | 将一个字符串转换成一个整数,要求不能使用字符串转换整数的库函数。 7 | 数值为0或者字符串不是一个合法的数值则返回0 8 | 9 | 输入描述: 10 | 输入一个字符串,包括数字字母符号,可以为空 11 | 12 | 输出描述: 13 | 如果是合法的数值表达则返回该数字,否则返回0 14 | 15 | 代码结构: 16 | class Solution 17 | { 18 | public int StrToInt(string str) 19 | { 20 | // write code here 21 | } 22 | } 23 | */ 24 | using System; 25 | namespace StrToInt { 26 | 27 | class Solution { 28 | 29 | /// 30 | /// 解法1 31 | /// 基本思路: 32 | /// 按顺序遍历字符串,判断每个字符是否是合法数字字符(在'0'-'9'之间) 33 | /// 如果都是合法字符,计算出所表示的数值 34 | /// 注意需要处理int类型的越界问题 35 | /// 36 | 37 | public int StrToInt(string str) 38 | { 39 | if (str == null){ 40 | return 0; 41 | } 42 | int num = 0, step = 1; 43 | for(int i = str.Length - 1; i >= 0; i --){ 44 | int n = (int)str[i] - 48; 45 | if (n >= 0 && n <= 9){ 46 | num += step * n; 47 | if(num < 0 && ((num - 1) < 0 || i == 0)){ 48 | return 0; 49 | } 50 | step *= 10; 51 | }else if(i == 0){ 52 | if (str[i] == '-'){ 53 | num = -num; 54 | }else if(str[i] != '+' || num < 0){ 55 | return 0; 56 | } 57 | }else{ 58 | return 0; 59 | } 60 | } 61 | return num; 62 | } 63 | 64 | /// 65 | /// 解法2 66 | /// 基本思路: 67 | /// 和解法1一样的思路,只是精简了代码,同时使用了位运算提高效率 68 | /// (ret << 1) + (ret << 3) = ret * 2 + ret * 8 = ret * 10 69 | /// str[i] & 0x0f 取str[i]的后四位,'0'-'9'的后四位正好是0 - 9 70 | /// 71 | 72 | public int StrToInt2(string str) { 73 | if (str == null || str.Length == 0){ 74 | return 0; 75 | } 76 | int sign = str[0] == '-' ? -1 : 1; 77 | int ret = 0; 78 | for(int i = (str[0] == '+' || str[0] == '-') ? 1 : 0; i < str.Length; i ++){ 79 | if (!(str[i] >= '0' && str[i] <= '9')) return 0; 80 | ret = (ret << 1) + (ret << 3) + (str[i] & 0x0f); 81 | if (ret < 0 && (!(ret == int.MinValue && str[0] == '-'))) return 0; 82 | } 83 | return sign * ret; 84 | } 85 | 86 | public void Test() { 87 | 88 | string str = "123"; 89 | // str = ""; 90 | // str = null; 91 | // str = "+125"; 92 | // str = "-26"; 93 | // str = "12a"; 94 | // str = "0"; 95 | // str = "-2147483649"; 96 | // str = "2147483649"; 97 | // str = "-2147483648"; 98 | // str = "+2147483648"; 99 | // str = "2147483648"; 100 | 101 | // Console.WriteLine(StrToInt(str)); 102 | Console.WriteLine(StrToInt2(str)); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /Leetcode/FindMedianSortedArrays.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 寻找两个正序数组的中位数 4 | 5 | 题目描述: 6 | 给定两个大小为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。 7 | 请你找出这两个正序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。 8 | 你可以假设 nums1 和 nums2 不会同时为空。 9 | 10 | 示例1: 11 | nums1 = [1, 3] 12 | nums2 = [2] 13 | 则中位数是 2.0 14 | 15 | 示例2: 16 | nums1 = [1, 2] 17 | nums2 = [3, 4] 18 | 则中位数是 (2 + 3)/2 = 2.5 19 | 20 | 代码结构: 21 | public class Solution { 22 | public double FindMedianSortedArrays(int[] nums1, int[] nums2) { 23 | 24 | } 25 | } 26 | 27 | 题目链接: 28 | https://leetcode-cn.com/problems/median-of-two-sorted-arrays/ 29 | 30 | 官方题解: 31 | https://leetcode-cn.com/problems/median-of-two-sorted-arrays/solution/xun-zhao-liang-ge-you-xu-shu-zu-de-zhong-wei-s-114/ 32 | */ 33 | using System; 34 | using System.Collections.Generic; 35 | namespace FindMedianSortedArrays { 36 | 37 | class Solution { 38 | 39 | /// 40 | /// 解法1,二分查找 41 | /// 基本思路: 42 | /// 如果对复杂度的要求中有log,通常都需要用到二分查找 43 | /// 这道题求取两个有序数组的中位数可以转换成寻找两个有序数组的第k小的数,其中k为(m + n) / 2 或者 (m + n ) / 2 + 1 44 | /// 假设两个有序数组A和B,要找到第k个元素,可以先比较A[k / 2 - 1] = Ai和B[k / 2 - 1] = Bj 45 | /// 比Ai和Bj小的元素总和最多有 (k / 2 - 1) + (k / 2 - 1) = k - 2个元素,所以这k - 2个元素一定不会是第k小的元素,都是可以舍弃的 46 | /// 如果Ai < Bj,那么Ai顶多就是第k - 1小的数,也可以舍弃,即Ai以及Ai前的元素都可以舍弃 47 | /// 如果Bj < Ai,那么Bj顶多就是第k - 1小的数,也可以舍弃,即Bj以及Bj前的元素都可以舍弃 48 | /// 49 | 50 | public double FindMedianSortedArraysImpl(int[] nums1, int i, int[] nums2, int j, int k) { 51 | if(nums1 == null || nums1.Length == 0 || i >= nums1.Length) return nums2[j + k - 1]; 52 | if(nums2 == null || nums2.Length == 0 || j >= nums2.Length) return nums1[i + k - 1]; 53 | if(k == 1) return Math.Min(nums1[i], nums2[j]); 54 | int index = k / 2 - 1; 55 | if(i + index >= nums1.Length) index = nums1.Length - i - 1; 56 | if(j + index >= nums2.Length) index = nums2.Length - j - 1; 57 | k = k - index - 1; 58 | if(nums1[i + index] <= nums2[j + index]) 59 | return FindMedianSortedArraysImpl(nums1, i + index + 1, nums2, j, k); 60 | else 61 | return FindMedianSortedArraysImpl(nums1, i, nums2, j + index + 1, k); 62 | } 63 | 64 | public double FindMedianSortedArrays(int[] nums1, int[] nums2) { 65 | int k = (nums1?.Length ?? 0) + (nums2?.Length ?? 0); 66 | if((k & 1) == 1) 67 | return FindMedianSortedArraysImpl(nums1, 0, nums2, 0, k / 2 + 1); 68 | else 69 | return (FindMedianSortedArraysImpl(nums1, 0, nums2, 0, k / 2) + FindMedianSortedArraysImpl(nums1, 0, nums2, 0, k / 2 + 1)) / 2; 70 | } 71 | 72 | public void Test() { 73 | int[] nums1 = new int[]{1, 2}; 74 | // nums1 = new int[]{1, 3}; 75 | // nums1 = new int[]{1, 4, 7}; 76 | // nums1 = new int[]{1}; 77 | // nums1 = null; 78 | 79 | int[] nums2 = new int[]{3, 4}; 80 | // nums2 = new int[]{2}; 81 | // nums2 = new int[]{4}; 82 | // nums2 = new int[]{2, 7, 9}; 83 | // nums2 = new int[]{2, 3, 4, 5, 6}; 84 | // nums2 = null; 85 | 86 | Console.WriteLine(FindMedianSortedArrays(nums1, nums2)); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /剑指offer/MaxInWindows.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 滑动窗口的最大值 4 | 5 | 题目描述: 6 | 给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。 7 | 例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口, 8 | 他们的最大值分别为{4,4,6,6,6,5}; 9 | 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: 10 | {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, 11 | {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。 12 | 13 | 代码结构: 14 | class Solution 15 | { 16 | public int[] maxInWindows(int[] num, int size) 17 | { 18 | // write code here 19 | } 20 | } 21 | */ 22 | using System; 23 | using System.Collections.Generic; 24 | namespace MaxInWindows { 25 | 26 | class Solution { 27 | 28 | /// 29 | /// 解法1 30 | /// 基本思路: 31 | /// 最直观的解法,针对每一个size大小的窗口,都重新计算出最大值 32 | /// 但效率不高,没有利用好前面窗口已经算出的最大值 33 | /// 34 | 35 | public int[] MaxInWindows(int[] num, int size) 36 | { 37 | if(num == null || num.Length < size || size <= 0) { 38 | return new int[]{}; 39 | } 40 | int[] ret = new int[num.Length - size + 1]; 41 | for(int i = 0; i < num.Length - size + 1; i ++){ 42 | int max = num[i]; 43 | for(int j = 1; j < size; j ++){ 44 | if(num[i + j] > max){ 45 | max = num[i + j]; 46 | } 47 | } 48 | ret[i] = max; 49 | } 50 | return ret; 51 | } 52 | 53 | /// 54 | /// 解法2,双端队列 55 | /// 基本思路: 56 | /// 利用双端队列的首部记录每个窗口的最大值 57 | /// 每新增一个元素 58 | /// 1. 从队列首部开始判断,每个元素是否已经超出窗口范围,是的话移除 59 | /// 2. 从队列尾部开始判断,新增的元素是否比队列内的元素大,是的话移除(保证队列首部一定是最大值) 60 | /// 3. 将新增的元素从尾部加入队列 61 | /// 62 | 63 | public int[] MaxInWindows2(int[] num, int size) 64 | { 65 | if(num == null || size <= 0 || num.Length < size){ 66 | return new int[]{}; 67 | } 68 | int[] ret = new int[num.Length - size + 1]; 69 | LinkedList list = new LinkedList(); 70 | for(int i = 0; i < num.Length; i ++){ 71 | while(list.Count > 0 && i - size >= list.First.Value) list.RemoveFirst(); 72 | while(list.Count > 0 && num[i] >= num[list.Last.Value]) list.RemoveLast(); 73 | list.AddLast(i); 74 | if(i - size + 1 >= 0){ 75 | ret[i - size + 1] = num[list.First.Value]; 76 | } 77 | } 78 | return ret; 79 | } 80 | 81 | public void Print(int[] num) { 82 | if(num == null){ 83 | Console.WriteLine("null"); 84 | return; 85 | } 86 | foreach(int i in num){ 87 | Console.WriteLine(i); 88 | } 89 | } 90 | 91 | public void Test() { 92 | 93 | int[] num = new int[]{2,3,4,2,6,2,5,1}; 94 | 95 | int size = 3; 96 | 97 | // Print(MaxInWindows(num, size)); 98 | Print(MaxInWindows2(num, size)); 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /剑指offer/Merge.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 合并两个排序的链表 4 | 5 | 题目描述: 6 | 输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。 7 | 8 | 代码结构: 9 | class Solution 10 | { 11 | public ListNode Merge(ListNode pHead1, ListNode pHead2) 12 | { 13 | // write code here 14 | } 15 | } 16 | */ 17 | using System; 18 | namespace Merge { 19 | 20 | public class ListNode 21 | { 22 | public int val; 23 | public ListNode next; 24 | public ListNode (int x) 25 | { 26 | val = x; 27 | } 28 | } 29 | 30 | class Solution { 31 | 32 | /// 33 | /// 解法1 34 | /// 基本思路: 35 | /// 同时遍历两个链表,比较两个链表的首结点,优先合并其中较小的节点 36 | /// 当两个链表长度不同时,最后再合并两个链表中较长链表的剩余节点 37 | /// 38 | 39 | public ListNode Merge(ListNode pHead1, ListNode pHead2) 40 | { 41 | ListNode pHead = new ListNode(0); 42 | ListNode head = pHead; 43 | while(pHead1 != null && pHead2 != null){ 44 | if(pHead2.val < pHead1.val){ 45 | head.next = pHead2; 46 | pHead2 = pHead2.next; 47 | }else{ 48 | head.next = pHead1; 49 | pHead1 = pHead1.next; 50 | } 51 | head = head.next; 52 | } 53 | if(pHead1 != null){ 54 | head.next = pHead1; 55 | } 56 | if(pHead2 != null){ 57 | head.next = pHead2; 58 | } 59 | return pHead.next; 60 | } 61 | 62 | /// 63 | /// 解法2,递归 64 | /// 基本思路: 65 | /// 首先算法合并两个链表头节点中较小的节点,即将较小的节点作为新链表的头结点 66 | /// 然后通过递归寻找新链表头结点的下一个节点,过程如下 67 | /// 如果链表1的头结点较小,则链表1向下走一步,链表1指向下一个节点,找到链表1与链表2中较小的头结点 68 | /// 如果链表2的头结点较小,则链表2向下走一步,链表2指向下一个节点,找到链表1与链表2中较小的头结点 69 | /// 70 | 71 | public ListNode Merge2(ListNode pHead1, ListNode pHead2) 72 | { 73 | if(pHead1 == null) return pHead2; 74 | if(pHead2 == null) return pHead1; 75 | if(pHead2.val < pHead1.val){ 76 | pHead2.next = Merge2(pHead1, pHead2.next); 77 | return pHead2; 78 | }else{ 79 | pHead1.next = Merge2(pHead1.next, pHead2); 80 | return pHead1; 81 | } 82 | } 83 | 84 | public void Print(ListNode head){ 85 | while(head != null){ 86 | Console.WriteLine(head.val); 87 | head = head.next; 88 | } 89 | } 90 | 91 | public void Test() { 92 | 93 | ListNode pHead1 = new ListNode(1); 94 | pHead1.next = new ListNode(2); 95 | pHead1.next.next = new ListNode(3); 96 | pHead1.next.next.next = new ListNode(4); 97 | // pHead1 = null; 98 | 99 | ListNode pHead2 = new ListNode(0); 100 | pHead2.next = new ListNode(3); 101 | pHead2.next.next = new ListNode(4); 102 | pHead2.next.next.next = new ListNode(5); 103 | // pHead2 = null; 104 | 105 | // Print(Merge(pHead1, pHead2)); 106 | Print(Merge2(pHead1, pHead2)); 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /剑指offer/Mirror.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 二叉树的镜像 4 | 5 | 题目描述: 6 | 操作给定的二叉树,将其变换为源二叉树的镜像。 7 | 8 | 输入描述: 9 | 二叉树的镜像定义:源二叉树 10 | 8 11 | / \ 12 | 6 10 13 | / \ / \ 14 | 5 7 9 11 15 | 镜像二叉树 16 | 8 17 | / \ 18 | 10 6 19 | / \ / \ 20 | 11 9 7 5 21 | 22 | 代码结构: 23 | class Solution 24 | { 25 | public TreeNode Mirror(TreeNode root) 26 | { 27 | // write code here 28 | } 29 | } 30 | */ 31 | using System; 32 | using System.Collections.Generic; 33 | namespace Mirror { 34 | 35 | public class TreeNode 36 | { 37 | public int val; 38 | public TreeNode left; 39 | public TreeNode right; 40 | public TreeNode (int x) 41 | { 42 | val = x; 43 | } 44 | } 45 | 46 | class Solution { 47 | 48 | /// 49 | /// 解法1,递归 50 | /// 基本思路: 51 | /// 先翻转根节点的左右子节点,然后通过递归再分别翻转左右子节点的子节点 52 | /// 53 | 54 | public TreeNode Mirror(TreeNode root) 55 | { 56 | if(root != null){ 57 | TreeNode node = root.left; 58 | root.left = root.right; 59 | root.right = node; 60 | Mirror(root.left); 61 | Mirror(root.right); 62 | } 63 | return root; 64 | } 65 | 66 | /// 67 | /// 解法2,非递归 68 | /// 利用栈结构遍历每一个节点,替换该节点的左右子节点 69 | /// 70 | 71 | public TreeNode Mirror2(TreeNode root) 72 | { 73 | Stack stack = new Stack(); 74 | stack.Push(root); 75 | while(stack.Count > 0){ 76 | TreeNode node = stack.Pop(); 77 | if(node != null){ 78 | TreeNode temp = node.left; 79 | node.left = node.right; 80 | node.right = temp; 81 | stack.Push(node.left); 82 | stack.Push(node.right); 83 | } 84 | } 85 | return root; 86 | } 87 | 88 | public void Print(TreeNode root){ 89 | Queue queue = new Queue(); 90 | queue.Enqueue(root); 91 | while(queue.Count > 0){ 92 | TreeNode node = queue.Dequeue(); 93 | if(node == null){ 94 | Console.Write("# "); 95 | } else{ 96 | Console.Write(node.val + " "); 97 | queue.Enqueue(node.left); 98 | queue.Enqueue(node.right); 99 | } 100 | } 101 | Console.WriteLine(); 102 | } 103 | 104 | public void Test() { 105 | 106 | TreeNode root = new TreeNode(8); 107 | root.left = new TreeNode(6); 108 | root.right = new TreeNode(10); 109 | // root.left.left = new TreeNode(5); 110 | // root.left.right = new TreeNode(7); 111 | root.right.left = new TreeNode(9); 112 | // root.right.right = new TreeNode(11); 113 | // root = null; 114 | 115 | // Print(Mirror(root)); 116 | Print(Mirror2(root)); 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /剑指offer/PrintListFromTailToHead.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 从尾到头打印链表 4 | 5 | 题目描述: 6 | 输入一个链表,按链表从尾到头的顺序返回一个ArrayList。 7 | 8 | 代码结构: 9 | class Solution 10 | { 11 | // 返回从尾到头的列表值序列 12 | public List PrintListFromTailToHead(ListNode listNode) 13 | { 14 | // write code here 15 | } 16 | } 17 | */ 18 | using System; 19 | using System.Collections.Generic; 20 | namespace PrintListFromTailToHead { 21 | 22 | public class ListNode 23 | { 24 | public int val; 25 | public ListNode next; 26 | public ListNode (int x) 27 | { 28 | val = x; 29 | } 30 | } 31 | 32 | class Solution { 33 | 34 | /// 35 | /// 解法1 36 | /// 基本思路: 37 | /// while循环从头遍历整个链表,将每个元素插入到List中 38 | /// 因为要求是从尾到头,所以每次插入时利用Insert函数不断将元素插入到第一的位置 39 | /// 40 | public List PrintListFromTailToHead(ListNode listNode) 41 | { 42 | List list = new List(); 43 | while(listNode != null){ 44 | list.Insert(0, listNode.val); 45 | listNode = listNode.next; 46 | } 47 | return list; 48 | } 49 | 50 | /// 51 | /// 解法2 52 | /// 基本思路: 53 | /// 和解法1类似,遍历每个元素后通过Add函数添加到List中,最后统一调用一次Reverse方法进行翻转 54 | /// 55 | public List PrintListFromTailToHead2(ListNode listNode) 56 | { 57 | List list = new List(); 58 | while(listNode != null){ 59 | list.Add(listNode.val); 60 | listNode = listNode.next; 61 | } 62 | list.Reverse(); 63 | return list; 64 | } 65 | 66 | /// 67 | /// 解法3 68 | /// 基本思路: 69 | /// 利用递归,不断的查找链表的下一个节点,直到尾结点。然后在回溯过程中将每个节点的值加入到List中 70 | /// 71 | public List PrintListFromTailToHead3(ListNode listNode) 72 | { 73 | List list = new List(); 74 | PrintListFromTailToHead3Impl(list, listNode); 75 | return list; 76 | } 77 | 78 | public void PrintListFromTailToHead3Impl(List list, ListNode listNode) 79 | { 80 | if(listNode == null) return; 81 | PrintListFromTailToHead3Impl(list, listNode.next); 82 | list.Add(listNode.val); 83 | } 84 | 85 | public void Print(List list){ 86 | if(list == null){ 87 | Console.WriteLine("null"); 88 | }else{ 89 | foreach (var item in list) 90 | { 91 | Console.WriteLine(item); 92 | } 93 | } 94 | } 95 | 96 | public void Test() { 97 | 98 | ListNode node = new ListNode(0); 99 | // node = null; 100 | node.next = new ListNode(3); 101 | node.next.next = new ListNode(1); 102 | 103 | // Print(PrintListFromTailToHead(node)); 104 | // Print(PrintListFromTailToHead2(node)); 105 | Print(PrintListFromTailToHead3(node)); 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /剑指offer/PrintMatrix.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 顺时针打印矩阵 4 | 5 | 题目描述: 6 | 输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字, 7 | 例如,如果输入如下4 X 4矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 8 | 则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10. 9 | 10 | 代码结构: 11 | class Solution 12 | { 13 | public List printMatrix(int[][] matrix) 14 | { 15 | // write code here 16 | } 17 | } 18 | */ 19 | using System; 20 | using System.Collections.Generic; 21 | namespace PrintMatrix { 22 | 23 | class Solution { 24 | 25 | /// 26 | /// 解法 27 | /// 基本思路: 28 | /// 1 2 3 4 29 | /// 5 6 7 8 30 | /// 9 10 11 12 31 | /// 13 14 15 16 32 | /// 顺时针打印就是将矩阵按圈顺时针循环打印,比如上面的4x4矩阵可以看成是循环两圈 33 | /// 分别是外圈1,2,3,4,8,12,16,15,14,13,9,5和内圈6,7,11,10 34 | /// 每一圈又可以分成四个部分 35 | /// 分别是从左到右遍历第一行,从上到下遍历最后一列, 36 | /// 从右到左遍历最后一行,从下到上遍历第一列 37 | /// 注意处理每部分不要重复遍历相同元素即可 38 | /// 39 | 40 | public List PrintMatrix(int[][] matrix) 41 | { 42 | List list = new List(); 43 | int offset = 0, row = matrix.Length, col = matrix[0].Length; 44 | while(offset < row && offset < col){ 45 | for(int i = offset; i < col; i ++) 46 | list.Add(matrix[offset][i]); 47 | for(int i = offset + 1; i < row; i ++) 48 | list.Add(matrix[i][col - 1]); 49 | for(int i = col - 2; i >= offset && offset != row - 1; i --) 50 | list.Add(matrix[row - 1][i]); 51 | for(int i = row - 2; i > offset && offset != col - 1; i --) 52 | list.Add(matrix[i][offset]); 53 | offset ++; 54 | row --; 55 | col --; 56 | } 57 | return list; 58 | } 59 | 60 | public void Print(List list){ 61 | if(list == null){ 62 | Console.WriteLine("null"); 63 | return; 64 | } 65 | foreach(int item in list){ 66 | Console.WriteLine(item); 67 | } 68 | } 69 | 70 | public void Test() { 71 | 72 | int[][] matrix = new int[][]{ 73 | new int[]{1, 2}, 74 | new int[]{3, 4}, 75 | }; 76 | 77 | // matrix = new int[][]{ 78 | // new int[]{1, 2, 3, 4}, 79 | // new int[]{5, 6, 7, 8}, 80 | // new int[]{9, 10, 11, 12}, 81 | // new int[]{13, 14, 15, 16}, 82 | // }; 83 | 84 | // matrix = new int[][]{ 85 | // new int[]{1, 2, 3}, 86 | // new int[]{4, 5, 6}, 87 | // }; 88 | 89 | // matrix = new int[][]{ 90 | // new int[]{1, 2}, 91 | // new int[]{3, 4}, 92 | // new int[]{5, 6}, 93 | // }; 94 | 95 | // matrix = new int[][]{ 96 | // new int[]{1}, 97 | // new int[]{2}, 98 | // new int[]{3}, 99 | // new int[]{4}, 100 | // new int[]{5}, 101 | // }; 102 | 103 | // matrix = new int[][]{ 104 | // new int[]{1, 2, 3, 4, 5}, 105 | // }; 106 | 107 | Print(PrintMatrix(matrix)); 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /常用算法/KMP.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 算法名称: 3 | KMP模式匹配算法 4 | 5 | 算法描述: 6 | 子串定位运算又称为模式匹配(Pattern Matching)或串匹配(String Matching)。 7 | KMP算法,是不需要对目标串S进行回溯的模式匹配算法。此算法是由D.E.Knuth,J.H.Morris和V.R.Pratt同时发现的, 8 | 因此该算法被称为克努斯-莫里斯-普拉特操作,简称为KMP算法。 9 | 匹配成功,返回模式串在目标串中的位置;或者目标串中不存在与模式串相等的子串,则匹配失败,返回-1. 10 | */ 11 | using System; 12 | namespace KMP { 13 | 14 | class Solution { 15 | 16 | /// 17 | /// 暴力匹配算法 Brute-Force 18 | /// 基本思路: 19 | /// 朴素的模式匹配算法,从目标串s的第一个字符开始和模式串p的第一个字符开始比较, 20 | /// 如果相等,则进一步比较二者的后继字符 21 | /// 如果不相等,则从目标串s的第二个字符开始再重新与模式串p的第一个字符进行比较 22 | /// 暴力匹配就在于当发生失配时,目标串直接回溯到开始匹配字符的下一个字符,模式串直接回溯到第一个字符 23 | /// 24 | public int BF(string s, string p){ 25 | int i = 0, j = 0; 26 | while(i < s.Length && j < p.Length) { 27 | if(s[i] == p[j]) { 28 | i ++; 29 | j ++; 30 | }else{ 31 | i = i - j + 1; 32 | j = 0; 33 | } 34 | } 35 | if(j == p.Length) { 36 | return i - j; 37 | } 38 | return -1; 39 | } 40 | 41 | /// 42 | /// 求解next数组 43 | /// 基本思路: 44 | /// next[j] = i就表示对于p[j]前面的子串(不包含p[j])有长度为i的前缀序列与后缀序列相等 45 | /// 即 p[0], p[1], p[2] .. p[i - 1] = p[j - i], p[j - i + 1], p[j - i + 2] .. p[j - 1] 46 | /// next数组求解详细解读 https://www.cnblogs.com/iwiniwin/p/10793659.html 47 | /// 48 | 49 | public int[] GetNext(string p){ 50 | int[] next = new int[p.Length]; 51 | int i = -1, j = 0; 52 | next[j] = i; 53 | while(j < p.Length - 1){ 54 | if(i == -1 || p[i] == p[j]){ 55 | i ++; 56 | j ++; 57 | if(p[i] == p[j]) 58 | next[j] = next[i]; // 优化,当p[j]失配时,由于p[i] = p[j],p[i]一定也失配,所以直接回溯到next[i] 59 | else 60 | next[j] = i; 61 | }else{ 62 | i = next[i]; 63 | } 64 | } 65 | return next; 66 | } 67 | 68 | /// 69 | /// 基本思路: 70 | /// 在模式匹配过程中,当匹配失败时,保证对目标串s不进行回溯 71 | /// 模式串p回溯到哪里通过next数组的值来确定 72 | /// KMP算法详细解读 https://www.cnblogs.com/iwiniwin/p/10793659.html 73 | /// 74 | 75 | public int KMP(string s, string p){ 76 | int i = 0, j = 0; 77 | int[] next = GetNext(p); 78 | while(i < s.Length && j < p.Length){ 79 | if(j == -1 || s[i] == p[j]){ 80 | i ++; 81 | j ++; 82 | }else{ 83 | j = next[j]; // 根据next数组的指引对p进行回溯 84 | } 85 | } 86 | if(j == p.Length) 87 | return i - j; 88 | else 89 | return -1; 90 | } 91 | 92 | public void Test() { 93 | 94 | string s = "ababb"; 95 | s = "ababcabcacb"; 96 | // s = "aaaabcde"; 97 | 98 | string p = "abb"; 99 | p = "abc"; 100 | p = "abcac"; 101 | // p = "aaaaax"; 102 | 103 | // Console.WriteLine(BF(s, p)); 104 | 105 | Console.WriteLine(KMP(s, p)); 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /剑指offer/Duplicate.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 数组中重复的数字 4 | 5 | 题目描述: 6 | 在一个长度为n的数组里的所有数字都在0到n-1的范围内。 7 | 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。 8 | 请找出数组中任意一个重复的数字。 9 | 例如,如果输入长度为7的数组[2,3,1,0,2,5,3],那么对应的输出是2或者3。存在不合法的输入的话输出-1 10 | 11 | 代码结构: 12 | class Solution { 13 | public int duplicate (List numbers) { 14 | // write code here 15 | } 16 | } 17 | */ 18 | using System; 19 | using System.Collections.Generic; 20 | namespace Duplicate { 21 | 22 | class Solution { 23 | 24 | /// 25 | /// 解法1 26 | /// 基本思路: 27 | /// 利用长度为n的辅助数组记录每个数字的出现次数 28 | /// 29 | 30 | public int Duplicate(List numbers) 31 | { 32 | if (numbers == null){ 33 | return -1; 34 | } 35 | int[] array = new int[numbers.Count]; 36 | for(int i = 0; i < numbers.Count; i ++){ 37 | if(++ array[numbers[i]] > 1){ 38 | return numbers[i]; 39 | } 40 | } 41 | return -1; 42 | } 43 | 44 | /// 45 | /// 解法2 46 | /// 基本思路: 47 | /// 不使用辅助数组,利用原数组保存遍历的信息 48 | /// 当一个数字被访问后,将该数字作为下标位置上的数减去数组的长度(使这个数一定小于0) 49 | /// 之后再遇到相同的数字时,发现对应的下标位置上的数已经小于0,则说明出现了重复元素 50 | /// 51 | 52 | public int Duplicate2(List numbers) 53 | { 54 | if (numbers == null){ 55 | return -1; 56 | } 57 | for(int i = 0; i < numbers.Count; i ++){ 58 | int num = numbers[i] < 0 ? numbers[i] + numbers.Count : numbers[i]; 59 | if(numbers[num] < 0){ 60 | return num; 61 | } 62 | numbers[num] -= numbers.Count; 63 | } 64 | return -1; 65 | } 66 | 67 | /// 68 | /// 解法3 69 | /// 基本思路: 70 | /// 不使用辅助数组 71 | /// 每访问到一个数字,判断这个数字与其下标是否相等,若不相等,则将该数字与以该数字值为下标的位置上的数字进行交换 72 | /// 如果要交换的两个数字相等,则找到了重复数字 73 | /// 可以理解为,让每个数字都在以其值为下标的位置上,如果有重复的数字,那它的位置一定会被重复值占领 74 | /// 75 | 76 | public int Duplicate3(List numbers) 77 | { 78 | if (numbers == null){ 79 | return -1; 80 | } 81 | for(int i = 0; i < numbers.Count; i ++){ 82 | while(i != numbers[i]){ 83 | int temp = numbers[numbers[i]]; 84 | if(numbers[i] == temp){ 85 | return numbers[i]; 86 | } 87 | numbers[numbers[i]] = numbers[i]; 88 | numbers[i] = temp; 89 | } 90 | } 91 | return -1; 92 | } 93 | 94 | public void Test() { 95 | 96 | List numbers = new List{2,3,1,0,2,5,3}; 97 | // numbers = null; 98 | // numbers = new List{}; 99 | // numbers = new List{0}; 100 | // numbers = new List{0, 0}; 101 | // numbers = new List{0, 2, 3, 3, 2}; 102 | 103 | Console.WriteLine(Duplicate(numbers)); 104 | // Console.WriteLine(Duplicate2(numbers)); 105 | // Console.WriteLine(Duplicate3(numbers)); 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /剑指offer/KthNode.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 二叉搜索树的第k个结点 4 | 5 | 题目描述: 6 | 给定一棵二叉搜索树,请找出其中的第k小的结点。 7 | 例如,(5,3,7,2,4,6,8)中,按结点数值大小顺序第三小结点的值为4。 8 | 9 | 代码结构: 10 | class Solution 11 | { 12 | public TreeNode KthNode(TreeNode pRoot, int k) 13 | { 14 | // write code here 15 | } 16 | } 17 | 18 | 补充: 19 | 二叉搜索树(Binary Search Tree)定义: 20 | 1. 可以是空树 21 | 2. 若不是空树 22 | 若它的左子树不空,则左子树所有节点的值均小于它的根节点的值 23 | 若它的右子树不空,则右子树所有节点的值均大于它的根节点的值 24 | 它的左,右子树也分别为二叉搜索树 25 | */ 26 | using System; 27 | using System.Collections.Generic; 28 | namespace KthNode { 29 | 30 | public class TreeNode 31 | { 32 | public int val; 33 | public TreeNode left; 34 | public TreeNode right; 35 | public TreeNode (int x) 36 | { 37 | val = x; 38 | } 39 | } 40 | 41 | class Solution { 42 | 43 | /// 44 | /// 解法1 45 | /// 基本思路: 46 | /// 根据二叉搜索树定义,采用中序遍历(先左节点,再根节点,再右节点) 47 | /// 找到的第k个节点,就是第k小的节点 48 | /// 49 | 50 | int index = 0; 51 | public TreeNode KthNode(TreeNode pRoot, int k) 52 | { 53 | if(pRoot != null){ 54 | TreeNode node = KthNode(pRoot.left, k); 55 | if(node != null){ 56 | return node; 57 | } 58 | 59 | index ++; 60 | if(index == k){ 61 | return pRoot; 62 | } 63 | 64 | node = KthNode(pRoot.right, k); 65 | if(node != null){ 66 | return node; 67 | } 68 | } 69 | return null; 70 | } 71 | 72 | /// 73 | /// 解法2 74 | /// 基本思路: 75 | /// 中序遍历的非递归实现,利用辅助栈,优先入栈左节点,再入栈 出栈节点的右节点 76 | /// 每次出栈,相当于找到一个节点,找到的第k个节点即为第k小的节点 77 | /// 78 | 79 | public TreeNode KthNode2(TreeNode pRoot, int k) 80 | { 81 | if(pRoot == null) return null; 82 | Stack stack = new Stack(); 83 | stack.Push(pRoot); 84 | TreeNode node = pRoot.left; 85 | while(stack.Count > 0 || node != null){ 86 | if(node == null){ 87 | node = stack.Pop(); 88 | if(--k == 0){ 89 | return node; 90 | } 91 | node = node.right; 92 | }else{ 93 | stack.Push(node); 94 | node = node.left; 95 | } 96 | } 97 | return null; 98 | } 99 | 100 | public void Test() { 101 | TreeNode pRoot = null; 102 | pRoot = new TreeNode(5); 103 | pRoot.left = new TreeNode(3); 104 | pRoot.right = new TreeNode(7); 105 | pRoot.left.left = new TreeNode(2); 106 | pRoot.left.right = new TreeNode(4); 107 | pRoot.right.left = new TreeNode(6); 108 | pRoot.right.right = new TreeNode(8); 109 | 110 | // TreeNode node = KthNode(pRoot, 1); 111 | TreeNode node = KthNode2(pRoot, 5); 112 | if(node == null){ 113 | Console.WriteLine("null"); 114 | }else{ 115 | Console.WriteLine(node.val); 116 | } 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /剑指offer/FindNumsAppearOnce.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 数组中只出现一次的数字 4 | 5 | 题目描述: 6 | 一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。 7 | 8 | 代码结构: 9 | //num1,num2分别为长度为1的数组。传出参数 10 | //将num1[0],num2[0]设置为返回结果 11 | class Solution 12 | { 13 | public void FindNumsAppearOnce(int[] array, int[] num1, int[] num2) 14 | { 15 | // write code here 16 | } 17 | } 18 | */ 19 | using System; 20 | using System.Collections.Generic; 21 | namespace FindNumsAppearOnce { 22 | 23 | class Solution { 24 | 25 | /// 26 | /// 解法1 27 | /// 基本思路: 28 | /// 遍历数组,统计每个数字出现的次数,然后返回出现次数等于1的数字 29 | /// 30 | 31 | public void FindNumsAppearOnce(int[] array, int[] num1, int[] num2) 32 | { 33 | if(array == null){ 34 | return; 35 | } 36 | Dictionary dic = new Dictionary(); 37 | for(int i = 0; i < array.Length; i ++){ 38 | if(dic.ContainsKey(array[i])){ 39 | dic[array[i]] ++; 40 | }else{ 41 | dic[array[i]] = 1; 42 | } 43 | } 44 | bool flag = false; 45 | foreach(KeyValuePair pair in dic){ 46 | if(pair.Value == 1){ 47 | if (!flag){ 48 | num1[0] = pair.Key; 49 | flag = true; 50 | }else{ 51 | num2[0] = pair.Key; 52 | } 53 | } 54 | } 55 | } 56 | 57 | /// 58 | /// 解法2,异或运算 59 | /// 基本思路: 60 | /// 利用异或运算的性质,两个相同的数字异或为0,任何数字和0异或都是其本身 61 | /// 题目提到除了两个只出现一次的数字A,B以外,其他数字都出现了两次,那么将数组中的所有元素进行异或运算 62 | /// 得到的结果ret一定就是A,B异或的值(根据上面两条异或的性质) 63 | /// 对于这个结果ret的二进制值按位遍历,找到第一个1所在的位置,记为index(这个1表示的是A,B中不同的位) 64 | /// 再遍历一次数组,根据每个元素的二进制index位置是否为1,进行分组。 65 | /// 每两个相同的元素一定都分在了一组,A,B一定不在一组 66 | /// 然后再对这两组元素按照最开始的思路依次异或,得到的值一定就是A和B 67 | /// 68 | 69 | public void FindNumsAppearOnce2(int[] array, int[] num1, int[] num2) 70 | { 71 | if(array == null){ 72 | return; 73 | } 74 | int ret = 0; 75 | foreach(int i in array){ 76 | ret ^= i; 77 | } 78 | int index = 0; 79 | while(ret > 0 && (ret & 1) == 0){ 80 | ret >>= 1; 81 | index ++; 82 | } 83 | foreach(int i in array){ 84 | if((i >> index & 1) == 1){ 85 | num1[0] ^= i; 86 | }else{ 87 | num2[0] ^= i; 88 | } 89 | } 90 | } 91 | 92 | public void Test() { 93 | 94 | int[] array = new int[]{}; 95 | // array = null; 96 | array = new int[]{1, 2, 3, 3, 2, 4, 4, 5}; 97 | // array = new int[]{1, 2, 2, 1, 3, 4, 4, 5, 6, 5}; 98 | // array = new int[]{1, 2}; 99 | 100 | 101 | int[] num1 = new int[1]; 102 | int[] num2 = new int[1]; 103 | // FindNumsAppearOnce(array, num1, num2); 104 | FindNumsAppearOnce2(array, num1, num2); 105 | 106 | Console.WriteLine(num1[0]); 107 | Console.WriteLine(num2[0]); 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /Leetcode/LongestPalindrome.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 最长回文子串 4 | 5 | 题目描述: 6 | 给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。 7 | 8 | 示例1: 9 | 输入: "babad" 10 | 输出: "bab" 11 | 注意: "aba" 也是一个有效答案。 12 | 13 | 示例2: 14 | 输入: "cbbd" 15 | 输出: "bb" 16 | 17 | 代码结构: 18 | public class Solution { 19 | public string LongestPalindrome(string s) { 20 | 21 | } 22 | } 23 | 24 | 题目链接: 25 | https://leetcode-cn.com/problems/longest-palindromic-substring/ 26 | 27 | 官方题解: 28 | https://leetcode-cn.com/problems/longest-palindromic-substring/solution/zui-chang-hui-wen-zi-chuan-by-leetcode-solution/ 29 | */ 30 | using System; 31 | using System.Collections.Generic; 32 | namespace LongestPalindrome { 33 | 34 | class Solution { 35 | 36 | /// 37 | /// 解法1,动态规划 38 | /// 基本思路: 39 | /// 分析回文串的特征,我们可以发现一个回文字符串的首尾两个元素一定是相等的 40 | /// 以P(i, j)表示字符串s的第i到第j个字母组成的字符串,P(i, j)是回文串的前提条件是s[i] == s[j]且P(i + 1, j - 1)是回文串 41 | /// 即动态规划的状态转移方程为 P(i, j) = s[i] == s[j] && P(i + 1, j - 1) 42 | /// 从0到s.Length - 1的循环,表示i和j之间相差的字母数可以是0 到 (s.Length - 1) 43 | /// 当相差字母数是0时,即单个字母,显然是回文串 44 | /// 当相差字母数是1时,即两个字母,只要第一个字母和第二个字母相同即是回文串 45 | /// 46 | 47 | public string LongestPalindrome(string s) { 48 | int len = s.Length; 49 | int start = 0, end = 0; 50 | bool[,] dp = new bool[len, len]; 51 | for(int n = 0; n < len; n ++){ 52 | for(int i = 0, j = i + n; j < len; i ++, j ++){ 53 | if(n == 0) dp[i, j] = true; 54 | else if(n == 1) dp[i, j] = s[i] == s[j]; 55 | else dp[i, j] = s[i] == s[j] && dp[i + 1, j - 1]; 56 | if(dp[i, j] && j - i > end - start) { 57 | start = i; end = j; 58 | } 59 | } 60 | } 61 | return len == 0 ? "" : s.Substring(start, end - start + 1); 62 | } 63 | 64 | /// 65 | /// 解法2,中心扩展 66 | /// 基本思路: 67 | /// 以最小的回文字符串作为回文中心,向两边扩散得到以该回文中心为中心的最长回文串 68 | /// 然后再比较所有得到的回文串,找到最长子回文串 69 | /// 回文中心的选取 70 | /// 1,以单一字母为中心,因为是左右两边同时扩展,所以可以涵盖bab型的回文串 71 | /// 2. 以两个字母为中心,可以涵盖aa型的回文串 72 | /// 所有的回文串都可以都过上述的两个中心扩展得到 73 | /// 74 | 75 | public int ExpandArroundCenter(string s, int i, int j){ 76 | while(i >= 0 && j < s.Length && s[i] == s[j]){ 77 | i --; 78 | j ++; 79 | } 80 | // 回文串的长度 j - i + 1 - 2 81 | return j - i - 1; 82 | } 83 | 84 | public string LongestPalindrome2(string s) { 85 | int start = 0, end = 0, len = s.Length; 86 | for(int i = 0; i < len; i ++){ 87 | int n = Math.Max(ExpandArroundCenter(s, i, i), ExpandArroundCenter(s, i, i + 1)); 88 | if(n > end - start){ 89 | start = i - (n - 1) / 2; end = i + n / 2; 90 | } 91 | } 92 | return len <= 1 ? s : s.Substring(start, end - start + 1); 93 | } 94 | 95 | public void Test() { 96 | string s = "babad"; 97 | // s = ""; 98 | // s = "cbbd"; 99 | // s = "aaaf"; 100 | // s = "aaaa"; 101 | // s = "a"; 102 | // s = "abacaba"; 103 | 104 | // Console.WriteLine(LongestPalindrome(s)); 105 | Console.WriteLine(LongestPalindrome2(s)); 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /剑指offer/FindContinuousSequence.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 和为S的连续正数序列 4 | 5 | 题目描述: 6 | 小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。 7 | 但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。 8 | 没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。 9 | 现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列? Good Luck! 10 | 11 | 输出描述: 12 | 输出所有和为S的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序 13 | 14 | 代码结构: 15 | using System.Collections.Generic; 16 | class Solution 17 | { 18 | public List> FindContinuousSequence(int sum) 19 | { 20 | // write code here 21 | } 22 | } 23 | */ 24 | using System; 25 | using System.Collections.Generic; 26 | namespace FindContinuousSequence { 27 | 28 | class Solution { 29 | 30 | /// 31 | /// 解法1 32 | /// 基本思路: 33 | /// 利用双指针,构造一个“窗口”,计算这个窗口内的元素之和(公式:(首+尾)*个数/2 ) 34 | /// 如果和等于sum,则将窗口内的元素加入到列表中,同时窗口整体右移,寻找下一个和等于sum的窗口 35 | /// 如果和小于sum,则right指针右移,增大窗口内的元素之和 36 | /// 如果和大于sum,则left指针右移,减小窗口内的元素之和 37 | /// 38 | 39 | public List> FindContinuousSequence(int sum) { 40 | List> lists = new List>(); 41 | int left = 1, right = 2; 42 | while(left < right && right < sum){ 43 | int cur = (left + right) * (right - left + 1) / 2; 44 | if(cur == sum){ 45 | List list = new List(); 46 | for(int i = left; i <= right; i ++){ 47 | list.Add(i); 48 | } 49 | lists.Add(list); 50 | left ++; 51 | right ++; 52 | }else if(cur < sum){ 53 | right ++; 54 | }else{ 55 | left ++; 56 | } 57 | } 58 | return lists; 59 | } 60 | 61 | /// 62 | /// 解法2 63 | /// 基本思路: 64 | /// 通过数列的平均值以及长度可以推导出数列 65 | /// 已知数列的和为sum,假设数列的长度为n 66 | /// 则n为奇数时,sum = 平均值 * n,即满足条件 sum % n == 0 67 | /// n为偶数时,sum = 平均值(为一个小数xx.5) * n,即 sum / n == xx.5 68 | /// 小数部分是0.5,说明余数是除数的一半,进一步得到 sum % n = n / 2 69 | /// 再来看n的取值范围,根据求和公式(1 + n) * n / 2 = sum 得到 n < sqrt(2 * sum) 70 | /// 71 | 72 | public List> FindContinuousSequence2(int sum) { 73 | List> lists = new List>(); 74 | for(int n = (int)Math.Sqrt(2 * sum); n >= 2; n --){ 75 | if((n & 1) == 1 && sum % n == 0 || sum % n * 2 == n){ 76 | List list = new List(); 77 | for(int i = sum / n - (n - 1) / 2, j = 0; j < n; i ++,j ++){ 78 | list.Add(i); 79 | } 80 | lists.Add(list); 81 | } 82 | } 83 | return lists; 84 | } 85 | 86 | public void Print(List> lists) { 87 | foreach(List list in lists){ 88 | foreach(int i in list){ 89 | Console.Write(i + " "); 90 | } 91 | Console.WriteLine(); 92 | } 93 | } 94 | 95 | public void Test() { 96 | 97 | int sum = 100; 98 | // sum = 102; 99 | // sum = 1; 100 | 101 | // List> lists = FindContinuousSequence(sum); 102 | List> lists = FindContinuousSequence2(sum); 103 | 104 | Print(lists); 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /剑指offer/GetLeastNumbers.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 最小的K个数 4 | 5 | 题目描述: 6 | 输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。 7 | 8 | 代码结构: 9 | class Solution 10 | { 11 | public List GetLeastNumbers_Solution(int[] input, int k) 12 | { 13 | // write code here 14 | } 15 | } 16 | 17 | 参考: 18 | https://blog.csdn.net/z50L2O08e2u4afToR9A/article/details/82837278 19 | */ 20 | using System; 21 | using System.Collections.Generic; 22 | namespace GetLeastNumbers { 23 | class Solution { 24 | 25 | /// 26 | /// 解法1 27 | /// 基本思路: 28 | /// 类似于直接选择排序,比较容易理解。每次遍历时找到子数组中的最小值,将最小值移动到子数组的起始位置。 29 | /// 30 | public void Swap(int[] input, int i, int j){ 31 | int temp = input[i]; 32 | input[i] = input[j]; 33 | input[j] = temp; 34 | } 35 | public List GetLeastNumbers_Solution(int[] input, int k) 36 | { 37 | List list = new List(); 38 | if (input == null || k > input.Length){ 39 | return list; 40 | } 41 | for(int i = 0; i < k && i < input.Length; i ++){ 42 | int index = i; 43 | for(int j = i + 1; j < input.Length; j ++){ 44 | if(input[j] < input[index]){ 45 | index = j; 46 | } 47 | } 48 | list.Add(input[index]); 49 | Swap(input, i, index); 50 | } 51 | return list; 52 | } 53 | 54 | /// 55 | /// 解法2 56 | /// 利用堆排序(小顶堆),首先构建堆,然后每次依次取堆顶的元素(最小值),并将其交换到尾部 57 | /// 然后重新构建堆,重复上述过程。取出k个数即可。 58 | /// 堆排序介绍 https://www.cnblogs.com/iwiniwin/p/12609549.html 59 | /// 60 | 61 | public void HeapAdjust(int[] input, int start, int end){ 62 | int target = input[start]; 63 | for(int i = 2 * start + 1; i < end; i = 2 * i + 1){ 64 | if ((i + 1) < end && input[i + 1] < input[i]){ 65 | i ++; 66 | } 67 | if (input[i] < target){ 68 | input[start] = input[i]; 69 | start = i; 70 | }else{ 71 | break; 72 | } 73 | } 74 | input[start] = target; 75 | } 76 | public List GetLeastNumbers_Solution2(int[] input, int k) 77 | { 78 | List list = new List(); 79 | if (input == null || input.Length < k){ 80 | return list; 81 | } 82 | for(int i = input.Length / 2 - 1; i >= 0; i --){ 83 | HeapAdjust(input, i, input.Length); 84 | } 85 | for(int i = 1; i <= k; i ++){ 86 | list.Add(input[0]); 87 | Swap(input, 0, input.Length - i); 88 | HeapAdjust(input, 0, input.Length - i); 89 | } 90 | return list; 91 | } 92 | 93 | public void Print(List list) { 94 | foreach (int item in list) 95 | { 96 | Console.WriteLine(item); 97 | } 98 | } 99 | 100 | public void Test() { 101 | int[] input = new int[]{4,5,1,6,2,7,3,8}; 102 | // input = new int[]{}; 103 | // input = null; 104 | 105 | // Print(GetLeastNumbers_Solution(input, 4)); 106 | Print(GetLeastNumbers_Solution2(input, 4)); 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /剑指offer/PrintTree2.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 把二叉树打印成多行 4 | 5 | 题目描述: 6 | 从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行 7 | 8 | 代码结构: 9 | class Solution 10 | { 11 | public List> Print(TreeNode pRoot) 12 | { 13 | // write code here 14 | } 15 | } 16 | */ 17 | using System; 18 | using System.Collections.Generic; 19 | namespace PrintTree2 { 20 | 21 | public class TreeNode 22 | { 23 | public int val; 24 | public TreeNode left; 25 | public TreeNode right; 26 | public TreeNode (int x) 27 | { 28 | val = x; 29 | } 30 | } 31 | 32 | class Solution { 33 | 34 | /// 35 | /// 解法1,层次遍历 36 | /// 基本思路: 37 | /// 利用一个辅助队列,队列中依次保存二叉树每一层的所有节点。 38 | /// 39 | 40 | public List> Print(TreeNode pRoot) 41 | { 42 | List> lists = new List>(); 43 | Queue queue = new Queue(); 44 | queue.Enqueue(pRoot); 45 | while(queue.Count > 0){ 46 | int count = queue.Count; 47 | List list = new List(); 48 | for(int i = 0; i < count; i ++){ 49 | TreeNode node = queue.Dequeue(); 50 | if(node != null){ 51 | list.Add(node.val); 52 | queue.Enqueue(node.left); 53 | queue.Enqueue(node.right); 54 | } 55 | } 56 | if(list.Count > 0){ 57 | lists.Add(list); 58 | } 59 | } 60 | return lists; 61 | } 62 | 63 | /// 64 | /// 解法2,递归 65 | /// 基本思路: 66 | /// 对二叉树进行先序遍历,即先根节点,再左节点,再右节点。保证每一层是从左到右顺序。 67 | /// 递归时利用depth记录二叉树的深度,即通过depth判断节点应该被加入到哪一层(lists[depth - 1]) 68 | /// 69 | 70 | public void Print2Impl(TreeNode node, int depth, List> lists){ 71 | if(node == null){ 72 | return; 73 | } 74 | if(depth > lists.Count){ 75 | lists.Add(new List()); 76 | } 77 | lists[depth - 1].Add(node.val); 78 | Print2Impl(node.left, depth + 1, lists); 79 | Print2Impl(node.right, depth + 1, lists); 80 | } 81 | 82 | public List> Print2(TreeNode pRoot) 83 | { 84 | List> lists = new List>(); 85 | Print2Impl(pRoot, 1, lists); 86 | return lists; 87 | } 88 | 89 | public void Dump(List> lists) { 90 | foreach(List list in lists){ 91 | foreach(int i in list){ 92 | Console.Write(i + " "); 93 | } 94 | Console.WriteLine(); 95 | } 96 | } 97 | 98 | public void Test() { 99 | TreeNode pRoot = null; 100 | pRoot = new TreeNode(0); 101 | pRoot.left = new TreeNode(1); 102 | pRoot.left.left = new TreeNode(2); 103 | pRoot.left.right = new TreeNode(3); 104 | pRoot.right = new TreeNode(4); 105 | pRoot.right.left = new TreeNode(5); 106 | pRoot.right.right = new TreeNode(6); 107 | pRoot.right.right.left = new TreeNode(7); 108 | pRoot.right.right.right = new TreeNode(8); 109 | 110 | // Dump(Print(pRoot)); 111 | Dump(Print2(pRoot)); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /剑指offer/RandomListNode.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 复杂链表的复制 4 | 5 | 题目描述: 6 | 输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针random指向一个随机节点) 7 | 请对此链表进行深拷贝,并返回拷贝后的头结点。 8 | (注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空) 9 | 10 | 代码结构: 11 | class Solution 12 | { 13 | public RandomListNode Clone(RandomListNode pHead) 14 | { 15 | // write code here 16 | } 17 | } 18 | */ 19 | using System; 20 | using System.Collections.Generic; 21 | namespace RandomListNode { 22 | 23 | public class RandomListNode 24 | { 25 | public int label; 26 | public RandomListNode next, random; 27 | public RandomListNode (int x) 28 | { 29 | this.label = x; 30 | } 31 | } 32 | 33 | class Solution { 34 | 35 | /// 36 | /// 解法1,递归 37 | /// 基本思路: 38 | /// 通过递归拷贝每一个节点 39 | /// 利用Dictionary处理循环链表的问题,避免死循环 40 | /// 41 | 42 | Dictionary dic = new Dictionary(); 43 | 44 | public RandomListNode Clone(RandomListNode pHead) 45 | { 46 | if(pHead == null) return null; 47 | if(dic.ContainsKey(pHead)) return dic[pHead]; 48 | RandomListNode head = new RandomListNode(pHead.label); 49 | dic[pHead] = head; 50 | head.next = Clone(pHead.next); 51 | head.random = Clone(pHead.random); 52 | return head; 53 | } 54 | 55 | /// 56 | /// 解法2,三步法 57 | /// 基本思路: 58 | /// 1. 复制链表每个节点,如:复制节点A得到A1,并将A1插入节点A后面 59 | /// 2. 重新遍历链表,为每个复制的节点设置random,如A1.random = A.random.next; 60 | /// 3、将链表拆分成原链表和复制后的链表 61 | /// 第2步是第1步将复制节点插入到原节点后面的原因,这样复制节点的random就是原节点random的下一个节点 62 | /// 可以查看docs/RandomListNode_solution2.png三步法的图示 63 | /// 64 | 65 | public RandomListNode Clone2(RandomListNode pHead){ 66 | if(pHead == null) return null; 67 | RandomListNode cur = pHead; 68 | while(cur != null){ 69 | RandomListNode next = new RandomListNode(cur.label); 70 | next.next = cur.next; 71 | cur.next = next; 72 | cur = next.next; 73 | } 74 | cur = pHead; 75 | while(cur != null){ 76 | if(cur.random != null) 77 | cur.next.random = cur.random.next; 78 | cur = cur.next.next; 79 | } 80 | cur = pHead; 81 | RandomListNode head = pHead.next, temp; 82 | while(cur != null && cur.next != null){ 83 | temp = cur.next; 84 | cur.next = temp.next; 85 | cur = temp; 86 | } 87 | return head; 88 | } 89 | 90 | public void Print(RandomListNode pHead){ 91 | if(pHead == null){ 92 | Console.WriteLine("null"); 93 | return; 94 | } 95 | while(pHead != null){ 96 | Console.WriteLine(pHead.label + " " + pHead.random?.label); 97 | pHead = pHead.next; 98 | } 99 | } 100 | 101 | public void Test() { 102 | 103 | RandomListNode pHead = new RandomListNode(1); 104 | pHead.next = new RandomListNode(2); 105 | pHead.next.next = new RandomListNode(3); 106 | pHead.next.next.next = new RandomListNode(4); 107 | pHead.random = pHead.next.next; 108 | pHead.next.random = pHead.next.next.next; 109 | 110 | // Print(Clone(pHead)); 111 | Print(Clone2(pHead)); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /剑指offer/FindNumbersWithSum.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 和为S的两个数字 4 | 5 | 题目描述: 6 | 输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S。 7 | 如果有多对数字的和等于S,输出两个数的乘积最小的。 8 | 9 | 输出描述: 10 | 对应每个测试案例,输出两个数,小的先输出。 11 | 12 | 代码结构: 13 | class Solution 14 | { 15 | public List FindNumbersWithSum(int[] array, int sum) 16 | { 17 | // write code here 18 | } 19 | } 20 | */ 21 | using System; 22 | using System.Collections.Generic; 23 | namespace FindNumbersWithSum { 24 | 25 | class Solution { 26 | 27 | /// 28 | /// 解法1 29 | /// 基本思路: 30 | /// 使用双指针left, right,左右逼近。题目要求乘积最小的先输出,实际上两个数离得越远,乘积越小 31 | /// 如果array[left] + array[right] == sum,得到答案 32 | /// 如果array[left] + array[right] < sum,left++,left指针右移 33 | /// 如果array[left] + array[right] > sum,right--,right指针左移 34 | /// 35 | 36 | public List FindNumbersWithSum(int[] array, int sum) 37 | { 38 | List list = new List(); 39 | if(array == null){ 40 | return list; 41 | } 42 | int left = 0, right = array.Length - 1; 43 | while(left < right){ 44 | int cur = array[left] + array[right]; 45 | if(cur == sum){ 46 | list.Add(array[left]); 47 | list.Add(array[right]); 48 | break; 49 | }else if(cur < sum){ 50 | left ++; 51 | }else{ 52 | right --; 53 | } 54 | } 55 | return list; 56 | } 57 | 58 | /// 59 | /// 解法1结合二分查找 60 | /// 基本思路: 61 | /// 本来直接使用解法1就可以AC了,不过我第一次提交的时候可能牛客出了什么问题,提示超时,所以有了这个结合二分查找的思路 62 | /// 利用三个指针start(指向左边的值),left, right。其中left和right通过二分查找,寻找右边的值 63 | /// 当右边的值未找到时,start++,start指针右移,start右边的数组继续进行二分查找 64 | /// 通过保证start指向的值是左边值中最小的来确定得到的两个数乘积最小 65 | /// 66 | 67 | public List FindNumbersWithSum2(int[] array, int sum) 68 | { 69 | List list = new List(); 70 | if(array == null){ 71 | return list; 72 | } 73 | int start = 0; 74 | int left = 1, right = array.Length - 1; 75 | while(start < right){ 76 | int mid = (left + right) / 2; 77 | int cur = array[start] + array[mid]; 78 | if(cur == sum){ 79 | list.Add(array[start]); 80 | list.Add(array[mid]); 81 | break; 82 | }else if(cur < sum){ 83 | left = mid + 1; 84 | }else{ 85 | right = mid - 1; 86 | } 87 | if(left > right){ 88 | start ++; 89 | left = start + 1; 90 | } 91 | } 92 | return list; 93 | } 94 | 95 | public void Print(List list) { 96 | foreach(int i in list){ 97 | Console.WriteLine(i); 98 | } 99 | } 100 | 101 | public void Test() { 102 | 103 | int[] array = new int[]{1, 2, 3, 3, 4, 4, 5, 6}; 104 | // array = null; 105 | 106 | int sum = 8; 107 | // sum = 7; 108 | // sum = 100; 109 | // sum = 1; 110 | 111 | List list = FindNumbersWithSum(array, sum); 112 | // List list = FindNumbersWithSum2(array, sum); 113 | Print(list); 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /Leetcode/ThreeSumClosest.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 最接近的三数之和 4 | 5 | 题目描述: 6 | 给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。 7 | 返回这三个数的和。假定每组输入只存在唯一答案。 8 | 9 | 示例: 10 | 输入:nums = [-1,2,1,-4], target = 1 11 | 输出:2 12 | 解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。 13 | 14 | 提示: 15 | 3 <= nums.length <= 10^3 16 | -10^3 <= nums[i] <= 10^3 17 | -10^4 <= target <= 10^4 18 | 19 | 代码结构: 20 | public class Solution { 21 | public int ThreeSumClosest(int[] nums, int target) { 22 | 23 | } 24 | } 25 | 26 | 题目链接: 27 | https://leetcode-cn.com/problems/3sum-closest/ 28 | 29 | 官方题解: 30 | https://leetcode-cn.com/problems/3sum-closest/solution/zui-jie-jin-de-san-shu-zhi-he-by-leetcode-solution/ 31 | */ 32 | using System; 33 | using System.Collections.Generic; 34 | namespace ThreeSumClosest { 35 | 36 | class Solution { 37 | 38 | /// 39 | /// 解法1 40 | /// 基本思路: 41 | /// 暴力解法,遍历所有的三数组合,找出最接近target的值 42 | /// 可以AC,没有超出时间限制,但时间复杂度较高O(n ^ 3) 43 | /// 44 | 45 | public int ThreeSumClosest(int[] nums, int target) { 46 | int ret = 0, diff = int.MaxValue; 47 | for(int i = 0; i < nums.Length - 2; i ++){ 48 | for(int j = i + 1; j < nums.Length - 1; j ++){ 49 | for(int k = j + 1; k < nums.Length; k ++){ 50 | int sum = nums[i] + nums[j] + nums[k]; 51 | int abs = Math.Abs(sum - target); 52 | if(abs < diff){ 53 | ret = sum; 54 | diff = abs; 55 | } 56 | } 57 | } 58 | } 59 | return ret; 60 | } 61 | 62 | /// 63 | /// 解法2,排序+双指针 64 | /// 基本思路: 65 | /// 这道题与ThreeSum.cs三数之和是类似的 66 | /// 首先需要对数组进行排序,因为对于没有任何规律的数组而言只能采用暴力解法,没有优化的空间 67 | /// 在枚举第一个元素之后,假设位置为i,则使左指针指向i + 1即左边界,使用右指针指向数组长度-1,即右边界 68 | /// 1. 如果a + b + c = target,则可以直接返回target,已经找到最接近的三数之和了 69 | /// 2. 如果a + b + c > target,则右指针向左移动,因为右指针右边的数据是逐渐增大的, 70 | /// 右边的元素显然会使三数之和更大于target,所以右边的数据都可以不用考虑了 71 | /// 3. 如果a + b + c < target,则左指针向右移动,因为左指针左边的数据是逐渐减小的, 72 | /// 左边的元素显然会使三数之和更小于target,所以左边的数据都可以不用考虑了 73 | /// 实际上双指针的思路,就是利用三数之和与target的关系,选择抛弃左边界还是右边界的元素,从而减少枚举范围 74 | /// 75 | 76 | public int ThreeSumClosest2(int[] nums, int target) { 77 | int ret = nums[0] + nums[1] + nums[2]; 78 | Array.Sort(nums); 79 | for(int n = 0; n < nums.Length; n ++){ 80 | if(n > 0 && nums[n] == nums[n - 1]) 81 | continue; 82 | int i = n + 1, j = nums.Length - 1; 83 | while(i < j){ 84 | int sum = nums[i] + nums[j] + nums[n]; 85 | if(sum == target) 86 | return sum; 87 | if(Math.Abs(sum - target) < Math.Abs(ret - target)) 88 | ret = sum; 89 | if(sum > target){ 90 | j --; 91 | }else{ 92 | i ++; 93 | } 94 | } 95 | } 96 | return ret; 97 | } 98 | 99 | public void Test() { 100 | int[] nums = new int[]{-1, 2, 1, -4}; 101 | // nums = new int[]{0, 0, 0}; 102 | 103 | 104 | int target = 1; 105 | // target = 5; 106 | // target = -4; 107 | 108 | Console.WriteLine(ThreeSumClosest(nums, target)); 109 | Console.WriteLine(ThreeSumClosest2(nums, target)); 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /剑指offer/ReverseSentence.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 翻转单词顺序列 4 | 5 | 题目描述: 6 | 牛客最近来了一个新员工Fish,每天早晨总是会拿着一本英文杂志,写些句子在本子上。 7 | 同事Cat对Fish写的内容颇感兴趣,有一天他向Fish借来翻看,但却读不懂它的意思。 8 | 例如,“student. a am I”。 9 | 后来才意识到,这家伙原来把句子单词的顺序翻转了,正确的句子应该是“I am a student.”。 10 | Cat对一一的翻转这些单词顺序可不在行,你能帮助他么? 11 | 12 | 代码结构: 13 | class Solution 14 | { 15 | public string ReverseSentence(string str) 16 | { 17 | // write code here 18 | } 19 | } 20 | */ 21 | using System; 22 | namespace ReverseSentence { 23 | 24 | class Solution { 25 | 26 | /// 27 | /// 解法1 28 | /// 基本思路: 29 | /// 利用Split函数将字符串根据" "拆分成多个子字符串,翻转子字符串的顺序,然后再用Join函数通过" "连接起来 30 | /// 31 | 32 | public void Reverse(string[] array, int m, int n){ 33 | for(int i = m, j = n; i < j; i++, j--){ 34 | string temp = array[i]; 35 | array[i] = array[j]; 36 | array[j] = temp; 37 | } 38 | } 39 | 40 | public string ReverseSentence(string str) 41 | { 42 | if (str == null){ 43 | return null; 44 | } 45 | string[] strs = str.Split(" "); 46 | Reverse(strs, 0, strs.Length - 1); 47 | return string.Join(" ", strs); 48 | } 49 | 50 | /// 51 | /// 解法2 52 | /// 基本思路: 53 | /// 一个一个字符处理,用tmp保存' '之前的字符串,遇到' '之后,将tmp添加到结果的前面 54 | /// 55 | /// 56 | /// 57 | 58 | public string ReverseSentence2(string str) 59 | { 60 | if (str == null){ 61 | return null; 62 | } 63 | string ret = "", tmp = ""; 64 | for(int i = 0; i < str.Length; i ++){ 65 | if(str[i] == ' '){ 66 | ret = ' ' + tmp + ret; 67 | tmp = ""; 68 | }else{ 69 | tmp += str[i]; 70 | } 71 | } 72 | ret = tmp + ret; 73 | return ret; 74 | } 75 | 76 | /// 77 | /// 解法3 78 | /// 基本思路: 79 | /// 先遍历字符数组找到每个单词,然后对每个单词进行翻转 80 | /// 最后再整体将整个字符数组进行翻转 81 | /// 82 | 83 | public string ReverseSentence3(string str) 84 | { 85 | if(str == null || str.Length == 0) return str; 86 | char[] array = str.ToCharArray(); 87 | int i = 0, j = 0; 88 | while(i < array.Length){ 89 | if(array[i] == ' '){ 90 | Reverse(array, j, i - 1); 91 | j = i + 1; 92 | } 93 | i ++; 94 | } 95 | Reverse(array, j, i - 1); 96 | Reverse(array, 0, array.Length - 1); 97 | return new string(array); 98 | } 99 | 100 | public void Reverse(char[] array, int i, int j){ 101 | while(i < j){ 102 | char temp = array[i]; 103 | array[i ++] = array[j]; 104 | array[j --] = temp; 105 | } 106 | } 107 | 108 | public void Test() { 109 | 110 | string str = "student. a am I"; 111 | // str = ""; 112 | // str = "am I"; 113 | // str = "am "; 114 | // str = null; 115 | str = "student. a am I"; 116 | 117 | // Console.WriteLine(ReverseSentence(str)); 118 | // Console.WriteLine(ReverseSentence2(str)); 119 | Console.WriteLine(ReverseSentence3(str)); 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /剑指offer/CutRope.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 剪绳子 4 | 5 | 题目描述: 6 | 给你一根长度为n的绳子,请把绳子剪成整数长的m段(m、n都是整数,n>1并且m>1), 7 | 每段绳子的长度记为k[0],k[1],...,k[m]。 8 | 请问k[0]xk[1]x...xk[m]可能的最大乘积是多少? 9 | 例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。 10 | 11 | 输入描述: 12 | 输入一个数n,意义见题面。(2 <= n <= 60) 13 | 14 | 输出描述: 15 | 输出答案。 16 | 17 | 代码结构: 18 | class Solution 19 | { 20 | public int cutRope(int number) 21 | { 22 | // write code here 23 | } 24 | } 25 | 26 | 补充: 27 | 动态规划求解问题的特征: 28 | 1. 求问题的最优解,整体的最优解依赖于子问题的最优解 29 | 2. 从上往下分析问题,从下往上求解问题 30 | */ 31 | using System; 32 | namespace CutRope { 33 | 34 | class Solution { 35 | 36 | /// 37 | /// 解法1 38 | /// 基本思路: 39 | /// 找规律,先列出n的前几项 40 | /// 2 1 * 1 41 | /// 3 1 * 2 42 | /// 4 2 * 2 43 | /// 5 2 * 3 44 | /// 6 3 * 3 45 | /// 7 2 * 2 * 3 46 | /// 8 2 * 3 * 3 47 | /// 9 3 * 3 * 3 48 | /// 10 2 * 2 * 3 * 3 49 | /// 11 2 * 3 * 3 * 3 50 | /// 可以发现除了特殊的n=2, n=3以外,其他值都是由2和3组成 51 | /// 问题在于如何确定2和3的个数 52 | /// 观察发现,2, 3的个数与 n / 3, n % 3 有关 53 | /// 2的个数要么是0个或1个或2个,不会是3个,因为当可以2 * 2 * 2 时应该用 3 * 3 表示 54 | /// 进一步总结可得n % 3 = 0 时 2的个数是0,3的个数是n/3 55 | /// n % 3 = 1时 2的个数是2,3的个数n/3 - 1 56 | /// 否则2的个数是1,3的个数是n/3 57 | /// 58 | 59 | public int CutRope(int number) 60 | { 61 | if(number == 2 || number == 3) return number - 1; 62 | int mod = number % 3; 63 | int div = number / 3; 64 | if(mod == 0) return (int)Math.Pow(3, div); 65 | else if(mod == 1) return 2 * 2 * (int)Math.Pow(3, div - 1); 66 | else return 2 * (int)Math.Pow(3, div); 67 | } 68 | 69 | /// 70 | /// 解法2,贪婪算法 71 | /// 基本思路: 72 | /// 根据解法1列出的n的前几项,可以发现,对于每个大于4的值来说,都希望分出更多的3 73 | /// 比如5 希望分成 2 * 3 74 | /// 比如8 希望分成 5 * 3,再分成 2 * 3 * 3 75 | /// 76 | 77 | public int CutRope2(int number) 78 | { 79 | if(number == 2 || number == 3) return number - 1; 80 | int ret = 1; 81 | while(number > 4){ 82 | ret *= 3; 83 | number -= 3; 84 | } 85 | return ret * number; 86 | } 87 | 88 | /// 89 | /// 解法3,动态规划 90 | /// 基本思路: 91 | /// 对于除n = 2和n = 3的特殊情况以外,使用dp数组记录前面的计算结果 92 | /// 求解n的最大乘积时,通过遍历每一种分段情况(分段从2开始,因为分出1的段的情况一定不是最大乘积), 93 | /// 取每种分段情况中乘积最大的为结果 94 | /// 再利用dp中的记录值,避免重复的计算 95 | /// 96 | 97 | public int CutRope3(int number) 98 | { 99 | if(number == 2 || number == 3) return number - 1; 100 | int[] dp = new int[number + 1]; 101 | dp[2] = 2; 102 | dp[3] = 3; 103 | int res = 0; 104 | for (int i = 4; i < number + 1; i++) 105 | { 106 | for(int j = 2; j < i / 2 + 1; j ++){ 107 | res = Math.Max(dp[i - j] * dp[j], res); 108 | } 109 | dp[i] = res; 110 | } 111 | return dp[number]; 112 | } 113 | 114 | 115 | 116 | public void Test() { 117 | 118 | int number = 2; 119 | // number = 3; 120 | // number = 5; 121 | // number = 6; 122 | // number = 7; 123 | // number = 30; 124 | 125 | // Console.WriteLine(CutRope(number)); 126 | // Console.WriteLine(CutRope2(number)); 127 | Console.WriteLine(CutRope3(number)); 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /剑指offer/GetMedian.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 数据流中的中位数 4 | 5 | 题目描述: 6 | 如何得到一个数据流中的中位数? 7 | 如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。 8 | 如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。 9 | 我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。 10 | 11 | 代码结构: 12 | class Solution 13 | { 14 | public void Insert(int num) 15 | { 16 | // write code here 17 | } 18 | 19 | public double GetMedian() 20 | { 21 | // write code here 22 | } 23 | } 24 | */ 25 | using System; 26 | namespace GetMedian { 27 | 28 | public class TreeNode { 29 | public TreeNode left; 30 | public TreeNode right; 31 | public int val; 32 | public TreeNode(int x){ 33 | val = x; 34 | } 35 | } 36 | 37 | class Solution { 38 | 39 | /// 40 | /// 解法1 41 | /// 基本思路: 42 | /// 插入时,动态构建二叉搜索树,小于根节点值的放到左子树,大于等于根节点值的放到右子树 43 | /// 获取中位数时查找儿叉搜索树即可 44 | /// 45 | 46 | TreeNode root = null; 47 | int count = 0; 48 | int index = 0; 49 | public void Insert(int num) 50 | { 51 | count ++; 52 | if(root == null){ 53 | root = new TreeNode(num); 54 | return; 55 | } 56 | TreeNode node = root; 57 | while(true){ 58 | if(num < node.val){ 59 | if(node.left == null || node.left.val < num){ 60 | TreeNode temp = node.left; 61 | node.left = new TreeNode(num); 62 | node.left.left = temp; 63 | return; 64 | } 65 | node = node.left; 66 | }else{ 67 | if(node.right == null || node.right.val > num){ 68 | TreeNode temp = node.right; 69 | node.right = new TreeNode(num); 70 | node.right.right = temp; 71 | return; 72 | } 73 | node = node.right; 74 | } 75 | } 76 | } 77 | 78 | public TreeNode GetKthNode(TreeNode pRoot, int k){ 79 | if(pRoot != null && k > 0){ 80 | TreeNode node = GetKthNode(pRoot.left, k); 81 | if(node != null){ 82 | return node; 83 | } 84 | index ++; 85 | if(index == k){ 86 | return pRoot; 87 | } 88 | node = GetKthNode(pRoot.right, k); 89 | if(node != null){ 90 | return node; 91 | } 92 | } 93 | return null; 94 | } 95 | 96 | public double GetMedian() 97 | { 98 | index = 0; 99 | if(count > 0){ 100 | int median = count / 2 + 1 ; 101 | TreeNode node = GetKthNode(root, median); 102 | if((count & 1) == 0){ 103 | index = 0; 104 | TreeNode last = GetKthNode(root, median - 1); 105 | return (last.val + node.val) / 2.0d; 106 | }else{ 107 | return node.val; 108 | } 109 | } 110 | return 0; 111 | } 112 | 113 | /// 114 | /// TODO 利用优先队列,构建两个堆,大顶堆和小顶堆 115 | /// 116 | 117 | public void Test() { 118 | 119 | Insert(1); 120 | Insert(3); 121 | Insert(4); 122 | Insert(1); 123 | 124 | Console.WriteLine(GetMedian()); 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /剑指offer/Power.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 数值的整数次方 4 | 5 | 题目描述: 6 | 给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。 7 | 8 | 保证base和exponent不同时为0 9 | 10 | 代码结构: 11 | class Solution 12 | { 13 | public double Power(double thebase, int exponent) 14 | { 15 | // write code here 16 | } 17 | } 18 | */ 19 | using System; 20 | namespace Power { 21 | 22 | class Solution { 23 | 24 | /// 25 | /// 解法1 26 | /// 基本思路: 27 | /// 最直接的思路,计算m的n次方,则将m连乘n次即可 28 | /// 注意处理特殊情况: 29 | /// 非0数的0次方等于1 30 | /// 当指数为负数时的结果,相当于用1除以指数为正数时的结果 31 | /// 注意:代码中使用了long y = exponent; 需要将exponent转换为long类型 32 | /// 是因为exponent可以等于-2147483648(int类型的最小值),直接进行-exponent 33 | /// 会导致越界(int类型最大值是2147483647),出现错误的结果 34 | /// 35 | 36 | public double Power(double thebase, int exponent) 37 | { 38 | if(thebase == 0) return 0; 39 | long y = exponent; 40 | if(y < 0){ 41 | thebase = 1 / thebase; 42 | y = -y; 43 | } 44 | double ret = 1; 45 | for(double i = 0; i < y; i ++){ 46 | ret *= thebase; 47 | } 48 | return ret; 49 | } 50 | 51 | /// 52 | /// 解法2,递归 53 | /// 基本思路: 54 | /// 假设指数为e,则通过递归每次求一半指数,即二分之e次方,再将递归的结果相乘得到指数e次方 55 | /// 通过e & 1 是否等于 1判断指数是奇数还是偶数,如果指数是奇数,则在递归结果相乘的基础上还要再乘以底数 56 | /// 57 | 58 | public double Power2(double thebase, int exponent) 59 | { 60 | if(thebase == 0) return 0; 61 | if(exponent == 0) return 1; 62 | long e = exponent; 63 | if(exponent < 0){ 64 | e = - e; 65 | thebase = 1 / thebase; 66 | } 67 | if(e == 1) return thebase; 68 | double ret = Power2(thebase, (int)(e >> 1)); 69 | return (e & 1) == 1 ? thebase * ret * ret : ret * ret; 70 | } 71 | 72 | /// 73 | /// 解法3,快速幂 74 | /// 基本思路: 75 | /// 求解整数m的n次方,一般是m ^ n = m * m * m .....,连乘n次,算法复杂度是O(n) 76 | /// 这样的算法效率太低,我们可以通过减少相乘的次数来提高算法效率,即快速幂 77 | /// 对于n我们可以用二进制表示,以14为例,14 = 1110 78 | /// m ^ (14) = m ^ (1110) = m ^ (2 ^ 3 * 1) * m ^ (2 ^ 2 * 1) * m ^ (2 ^ 1 * 1) * m ^ (2 ^ 0 * 0) 79 | /// = m ^ (8 * 1) * m ^ (4 * 1) * m ^ (2 * 1) * m ^ (1 * 0) = m ^ 8 * m ^ 4 * m ^ 2 * 1 80 | /// 可以发现这样的规律,指数n的二进制从低位到高位依次对应底数m的1次方,2次方,4次方,8次方..., 81 | /// 当该二进制位是1的时候,则乘以底数对应的次方数,如果该二进制位是0,则表示乘以1 82 | /// 83 | 84 | public double Power3(double thebase, int exponent) 85 | { 86 | if(thebase == 0) return 0; 87 | long y = exponent; 88 | if(y < 0){ 89 | thebase = 1 / thebase; 90 | y = -y; 91 | } 92 | 93 | double ret = 1; 94 | while(y > 0){ 95 | if((y & 1) == 1){ 96 | ret *= thebase; 97 | } 98 | thebase *= thebase; 99 | y >>= 1; 100 | } 101 | return ret; 102 | } 103 | 104 | public void Test() { 105 | 106 | double thebase = 3; 107 | // thebase = 0; 108 | // thebase = -3; 109 | // thebase = 0.5; 110 | // thebase = 2; 111 | 112 | 113 | int exponent = 4; 114 | exponent = 3; 115 | exponent = -3; 116 | // exponent = 0; 117 | exponent = -2147483648; 118 | 119 | // Console.WriteLine(Power(thebase, exponent)); 120 | // Console.WriteLine(Power2(thebase, exponent)); 121 | Console.WriteLine(Power3(thebase, exponent)); 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /剑指offer/MoreThanHalfNum.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 题目名称: 3 | 数组中出现次数超过一半的数字 4 | 5 | 题目描述: 6 | 数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。 7 | 例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。 8 | 如果不存在则输出0。 9 | 10 | 代码结构: 11 | class Solution 12 | { 13 | public int MoreThanHalfNum_Solution(int[] numbers) 14 | { 15 | // write code here 16 | } 17 | } 18 | */ 19 | using System; 20 | using System.Collections.Generic; 21 | namespace MoreThanHalfNum { 22 | class Solution { 23 | 24 | /*解法1 25 | 基本思路: 26 | 利用Dictinary记录每个元素的出现次数,如果该元素出现次数大于数组的一半则输出 27 | */ 28 | public int MoreThanHalfNum_Solution(int[] numbers) 29 | { 30 | if (numbers == null) return 0; 31 | Dictionary dic = new Dictionary(); 32 | foreach(int i in numbers){ 33 | if (dic.ContainsKey(i)){ 34 | dic[i] ++; 35 | }else{ 36 | dic.Add(i, 1); 37 | } 38 | if (dic[i] > numbers.Length / 2){ 39 | return i; 40 | } 41 | } 42 | return 0; 43 | } 44 | 45 | /*解法2 46 | 基本思路: 47 | 如果存在元素的数量大于数组的一半,那么排序后,数组中间的那个数一定是该元素。 48 | 比如{1,2,2,2,2,2,3,4,5} 49 | 排序后再判断数组中间的那个数出现次数是否大于数组长度的一半 50 | */ 51 | public int MoreThanHalfNum_Solution2(int[] numbers) 52 | { 53 | if (numbers != null && numbers.Length > 0){ 54 | Array.Sort(numbers); 55 | int num = numbers[numbers.Length / 2]; 56 | int count = 0; 57 | foreach(int i in numbers){ 58 | if (i == num){ 59 | count ++; 60 | } 61 | } 62 | if (count > numbers.Length / 2){ 63 | return num; 64 | } 65 | } 66 | return 0; 67 | } 68 | 69 | /*解法3 70 | 基本思路: 71 | 类似于阵地攻守, 72 | 第一个数字作为第一个士兵,守阵地 count = 1 73 | 遇到相同数字:count++ 74 | 遇到不同数字:count--,即遇到敌人 75 | 当遇到count = 0的情况,这表示该士兵阵亡,使用下一个数字代替 76 | 如果一个元素的数量超过数组长度的一半,则它的数量应该比其他剩余元素数量之和还要多,则剩下的那个数字就是该元素 77 | 最后再加一次循环,判断该元素数量是否查过数组长度的一半即可 78 | */ 79 | public int MoreThanHalfNum_Solution3(int[] numbers) 80 | { 81 | if (numbers != null && numbers.Length > 0){ 82 | int count = 0; 83 | int num = 0; 84 | foreach(int i in numbers){ 85 | if (count == 0){ 86 | num = i; 87 | count = 1; 88 | }else 89 | { 90 | if(i == num){ 91 | count ++; 92 | }else{ 93 | count --; 94 | } 95 | } 96 | } 97 | count = 0; 98 | foreach (int i in numbers) 99 | { 100 | if (i == num) { 101 | count ++; 102 | } 103 | } 104 | if (count > numbers.Length / 2) { 105 | return num; 106 | } 107 | } 108 | return 0; 109 | } 110 | 111 | public void Test() { 112 | int[] numbers = new int[]{1,2,3,2,2,2,5,4,2}; 113 | // numbers = null; 114 | // numbers = new int[]{0, 1, 0}; 115 | // Console.WriteLine(MoreThanHalfNum_Solution(numbers)); 116 | // Console.WriteLine(MoreThanHalfNum_Solution2(numbers)); 117 | // Console.WriteLine(MoreThanHalfNum_Solution3(numbers)); 118 | } 119 | } 120 | } 121 | --------------------------------------------------------------------------------