Git Product home page Git Product logo

leetcode's Introduction

LeetCode All In One

English | 简体中文


Provide all my solutions and explanations in Chinese for all the Leetcode coding problems.

Same as this: LeetCode All in One 题目讲解汇总(持续更新中...)

Click below image to watch YouTube Video Video

Note: All explanations are written in Github Issues, please do not create any new issue or pull request in this project since the problem index should be consistent with the issue index, thanks!

('$' means the problem is locked on Leetcode, '*' means the problem is related to Database, '#' means the problem is related to Shell, '~' means the concurrency problems.)

# Title Solution Difficulty
1352 Product of the Last K Numbers 50.20% Medium
1351 Count Negative Numbers in a Sorted Matrix 77.30% Easy
1350 Students With Invalid Departments * $ 89.90% Easy
1349 Maximum Students Taking Exam 49.80% Hard
1348 Tweet Counts Per Frequency 44.00% Medium
1347 Minimum Number of Steps to Make Two Strings Anagram 78.00% Medium
1346 Check If N and Its Double Exist 36.70% Easy
1345 Jump Game IV 46.60% Hard
1344 Angle Between Hands of a Clock 63.40% Medium
1343 Number of Sub-arrays of Size K and Average Greater than or Equal to Threshold 68.10% Medium
1342 Number of Steps to Reduce a Number to Zero 84.90% Easy
1341 Movie Rating * 43.80% Medium
1340 Jump Game V 62.60% Hard
1339 Maximum Product of Splitted Binary Tree 47.70% Medium
1338 Reduce Array Size to The Half 69.20% Medium
1337 The K Weakest Rows in a Matrix 71.40% Easy
1336 Number of Transactions per Visit * 48.10% Hard
1335 Minimum Difficulty of a Job Schedule 58.30% Hard
1334 Find the City With the Smallest Number of Neighbors at a Threshold Distance 57.70% Medium
1333 "Filter Restaurants by Vegan-Friendly Price and Distance" 60.50%
1332 Remove Palindromic Subsequences 76.20% Easy
1331 Rank Transform of an Array 59.80% Easy
1330 Reverse Subarray To Maximize Array Value 41.30% Hard
1329 Sort the Matrix Diagonally 83.00% Medium
1328 Break a Palindrome 52.10% Medium
1327 List the Products Ordered in a Period * 71.90% Easy
1326 Minimum Number of Taps to Open to Water a Garden 51.60% Hard
1325 Delete Leaves With a Given Value 74.40% Medium
1324 Print Words Vertically 62.40% Medium
1323 Maximum 69 Number 82.00% Easy
1322 Ads Performance * $ 60.10% Easy
1321 Restaurant Growth * 53.40% Medium
1320 Minimum Distance to Type a Word Using Two Fingers 59.80% Hard
1319 Number of Operations to Make Network Connected 58.50% Medium
1318 Minimum Flips to Make a OR b Equal to c 66.00% Medium
1317 Convert Integer to the Sum of Two No-Zero Integers 56.00% Easy
1316 Distinct Echo Substrings 49.70% Hard
1315 Sum of Nodes with Even-Valued Grandparent 85.60% Medium
1314 Matrix Block Sum 75.40% Medium
1313 Decompress Run-Length Encoded List 85.90% Easy
1312 Minimum Insertion Steps to Make a String Palindrome 65.70% Hard
1311 Get Watched Videos by Your Friends 45.90% Medium
1310 XOR Queries of a Subarray 72.20% Medium
1309 Decrypt String from Alphabet to Integer Mapping 79.50% Easy
1308 Running Total for Different Genders * $ 88.20% Medium
1307 Verbal Arithmetic Puzzle 34.80% Hard
1306 Jump Game III 63.10% Medium
1305 All Elements in Two Binary Search Trees 79.80% Medium
1304 Find N Unique Integers Sum up to Zero 77.10% Easy
1303 Find the Team Size * $ 90.80% Easy
1302 Deepest Leaves Sum 86.90% Medium
1301 Number of Paths with Max Score 38.70% Hard
1300 Sum of Mutated Array Closest to Target 43.10% Medium
1299 Replace Elements with Greatest Element on Right Side 74.70% Easy
1298 Maximum Candies You Can Get from Boxes 60.90% Hard
1297 Maximum Number of Occurrences of a Substring 52.00% Medium
1296 Divide Array in Sets of K Consecutive Numbers 56.60% Medium
1295 Find Numbers with Even Number of Digits 77.00% Easy
1294 Weather Type in Each Country * $ 68.00% Easy
1293 Shortest Path in a Grid with Obstacles Elimination 45.60% Hard
1292 Maximum Side Length of a Square with Sum Less than or Equal to Threshold 52.10% Medium
1291 Sequential Digits 60.90% Medium
1290 Convert Binary Number in a Linked List to Integer 82.70% Easy
1289 Minimum Falling Path Sum II 61.20% Hard
1288 Remove Covered Intervals 57.40% Medium
1287 Element Appearing More Than 25% In Sorted Array 59.50% Easy
1286 Iterator for Combination 73.20% Medium
1285 Find the Start and End Number of Continuous Ranges * $ 88.30% Medium
1284 Minimum Number of Flips to Convert Binary Matrix to Zero Matrix 72.00% Hard
1283 Find the Smallest Divisor Given a Threshold 53.70% Medium
1282 Group the People Given the Group Size They Belong To 85.20% Medium
1281 Subtract the Product and Sum of Digits of an Integer 86.30% Easy
1280 Students and Examinations * $ 74.60% Easy
1279 Traffic Light Controlled Intersection ~ $ 75.30% Easy
1278 Palindrome Partitioning III 60.90% Hard
1277 Count Square Submatrices with All Ones 74.20% Medium
1276 Number of Burgers with No Waste of Ingredients 50.60% Medium
1275 Find Winner on a Tic Tac Toe Game 54.90% Easy
1274 Number of Ships in a Rectangle $ 68.60% Hard
1273 Delete Tree Nodes $ 61.20% Medium
1272 Remove Interval $ 61.10% Medium
1271 Hexspeak $ 56.30% Easy
1270 All People Report to the Given Manager * $ 88.10% Medium
1269 Number of Ways to Stay in the Same Place After Some Steps 43.40% Hard
1268 Search Suggestions System 65.40% Medium
1267 Count Servers that Communicate 58.30% Medium
1266 Minimum Time Visiting All Points 79.20% Easy
1265 Print Immutable Linked List in Reverse $ 94.30% Medium
1264 Page Recommendations * $ 67.70% Medium
1263 Minimum Moves to Move a Box to Their Target Location 48.30% Hard
1262 Greatest Sum Divisible by Three 50.80% Medium
1261 Find Elements in a Contaminated Binary Tree 75.70% Medium
1260 Shift 2D Grid 62.10% Easy
1259 Handshakes That Don't Cross $ 54.40% Hard
1258 Synonymous Sentences $ 57.40% Medium
1257 Smallest Common Region $ 62.10% Medium
1256 Encode Number $ 69.00% Medium
1255 Maximum Score Words Formed by Letters 71.30% Hard
1254 Number of Closed Islands 62.70% Medium
1253 Reconstruct a 2-Row Binary Matrix 42.70% Medium
1252 Cells with Odd Values in a Matrix 78.50% Easy
1251 Average Selling Price * $ 83.20% Easy
1250 Check If It Is a Good Array 57.50% Hard
1249 Minimum Remove to Make Valid Parentheses 65.00% Medium
1248 Count Number of Nice Subarrays 57.60% Medium
1247 Minimum Swaps to Make Strings Equal 63.60% Medium
1246 Palindrome Removal $ 45.80% Hard
1245 Tree Diameter $ 62.00% Medium
1244 Design A Leaderboard $ 67.30% Medium
1243 Array Transformation $ 50.10% Easy
1242 Web Crawler Multithreaded ~ $ 48.20% Medium
1241 Number of Comments per Post * $ 68.00% Easy
1240 Tiling a Rectangle with the Fewest Squares 52.40% Hard
1239 Maximum Length of a Concatenated String with Unique Characters 50.70% Medium
1238 Circular Permutation in Binary Representation 67.90% Medium
1237 Find Positive Integer Solution for a Given Equation 69.70% Medium
1236 Web Crawler $ 65.40% Medium
1235 Maximum Profit in Job Scheduling 50.50% Hard
1234 Replace the Substring for Balanced String 35.50% Medium
1233 Remove Sub-Folders from the Filesystem 64.50% Medium
1232 Check If It Is a Straight Line 42.30% Easy
1231 Divide Chocolate $ 55.50% Hard
1230 Toss Strange Coins $ 50.80% Medium
1229 Meeting Scheduler $ 54.70% Medium
1228 Missing Number In Arithmetic Progression $ 51.20% Medium
1227 Airplane Seat Assignment Probability 63.00% Medium
1226 The Dining Philosophers ~ 60.20% Medium
1225 Report Contiguous Dates * 63.80% Hard
1224 Maximum Equal Frequency 36.20% Hard
1223 Dice Roll Simulation 47.40% Hard
1222 Queens That Can Attack the King 70.20% Medium
1221 Split a String in Balanced Strings 84.60% Easy
1220 Count Vowels Permutation 56.60% Hard
1219 Path with Maximum Gold 66.10% Medium
1218 Longest Arithmetic Subsequence of Given Difference 48.10% Medium
1217 Minimum Cost to Move Chips to The Same Position 70.70% Easy
1216 Valid Palindrome III $ 50.80% Hard
1215 Stepping Numbers $ 44.60% Medium
1214 Two Sum BSTs $ 67.30% Medium
1213 Intersection of Three Sorted Arrays $ 79.80% Easy
1212 Team Scores in Football Tournament * 57.00% Medium
1211 Queries Quality and Percentage * 70.40% Easy
1210 Minimum Moves to Reach Target with Rotations 47.50% Hard
1209 Remove All Adjacent Duplicates in String II 56.40% Medium
1208 Get Equal Substrings Within Budget 45.30% Medium
1207 Unique Number of Occurrences 72.40% Easy
1206 Design Skiplist 59.60% Hard
1205 Monthly Transactions II * 45.30% Medium
1204 Last Person to Fit in the Bus * 73.00% Medium
1203 Sort Items by Groups Respecting Dependencies 48.80% Hard
1202 Smallest String With Swaps 50.50% Medium
1201 Ugly Number III 27.20% Medium
1200 Minimum Absolute Difference 67.30% Easy
1199 Minimum Time to Build Blocks $ 39.40% Hard
1198 Find Smallest Common Element in All Rows $ 76.30% Medium
1197 Minimum Knight Moves $ 38.60% Medium
1196 How Many Apples Can You Put into the Basket $ 68.30% Easy
1195 Fizz Buzz Multithreaded ~ 71.40% Medium
1194 Tournament Winners * 52.70% Hard
1193 Monthly Transactions I * 68.70% Medium
1192 Critical Connections in a Network 51.80% Hard
1191 K-Concatenation Maximum Sum 24.60% Medium
1190 Reverse Substrings Between Each Pair of Parentheses 65.00% Medium
1189 Maximum Number of Balloons 62.00% Easy
1188 Design Bounded Blocking Queue ~ 73.20% Medium
1187 Make Array Strictly Increasing 43.80% Hard
1186 Maximum Subarray Sum with One Deletion 39.80% Medium
1185 Day of the Week 59.90% Easy
1184 Distance Between Bus Stops 53.90% Easy
1183 Maximum Number of Ones $ 58.70% Hard
1182 Shortest Distance to Target Color $ 54.30% Medium
1181 Before and After Puzzle $ 44.70% Medium
1180 Count Substrings with Only One Distinct Letter $ 78.30% Easy
1179 Reformat Department Table * 82.10% Easy
1178 Number of Valid Words for Each Puzzle 40.80% Hard
1177 Can Make Palindrome from Substring 36.50% Medium
1176 Diet Plan Performance $ 53.50% Easy
1175 Prime Arrangements 51.90% Medium
1174 Immediate Food Delivery II * 63.10% Medium
1173 Immediate Food Delivery I * 83.10% Easy
1172 Dinner Plate Stacks 36.40% Hard
1171 Remove Zero Sum Consecutive Nodes from Linked List 41.90% Medium
1170 Compare Strings by Frequency of the Smallest Character 60.60% Medium
1169 Invalid Transactions 30.40% Medium
1168 Optimize Water Distribution in a Village $ 62.30% Hard
1167 Minimum Cost to Connect Sticks $ 65.70% Medium
1166 Design File System $ 59.00% Medium
1165 Single-Row Keyboard $ 85.50% Easy
1164 Product Price at a Given Date * 69.00% Medium
1163 Last Substring in Lexicographical Order 36.00% Hard
1162 As Far from Land as Possible 46.60% Medium
1161 Maximum Level Sum of a Binary Tree 67.50% Medium
1160 Find Words That Can Be Formed by Characters 67.80% Easy
1159 Market Analysis II * 57.00% Hard
1158 Market Analysis I * 65.00% Medium
1157 Online Majority Element In Subarray 41.60% Hard
1156 Swap For Longest Repeated Character Substring 47.20% Medium
1155 Number of Dice Rolls With Target Sum 47.70% Medium
1154 Day of the Year 50.10% Easy
1153 String Transforms Into Another String $ 35.60% Hard
1152 Analyze User Website Visit Pattern $ 43.20% Medium
1151 Minimum Swaps to Group All 1's Together $ 51.90% Medium
1150 Check If a Number Is Majority Element in a Sorted Array $ 57.10% Easy
1149 Article Views II * $ 48.20% Medium
1148 Article Views I * $ 77.10% Easy
1147 Longest Chunked Palindrome Decomposition 59.80% Hard
1146 Snapshot Array 37.00% Medium
1145 Binary Tree Coloring Game 51.10% Medium
1144 Decrease Elements To Make Array Zigzag 46.50% Medium
1143 Longest Common Subsequence 58.80% Medium
1142 User Activity for the Past 30 Days II * $ 35.50% Easy
1141 User Activity for the Past 30 Days I * $ 54.60% Easy
1140 Stone Game II 64.60% Medium
1139 Largest 1-Bordered Square 48.70% Medium
1138 Alphabet Board Path 51.60% Medium
1137 N-th Tribonacci Number 55.70% Easy
1136 Parallel Courses $ 60.70% Medium
1135 Connecting Cities With Minimum Cost $ 60.00% Easy
1134 Armstrong Number $ 78.50% Easy
1133 Largest Unique Number $ 67.20% Easy
1132 Reported Posts II * $ 34.40% Medium
1131 Maximum of Absolute Value Expression 51.30% Medium
1130 Minimum Cost Tree From Leaf Values 67.40% Medium
1129 Shortest Path with Alternating Colors 40.70% Medium
1128 Number of Equivalent Domino Pairs 45.90% Easy
1127 User Purchase Platform * $ 50.80% Hard
1126 Active Businesses * $ 68.40% Medium
1125 Smallest Sufficient Team 47.20% Hard
1124 Longest Well-Performing Interval 33.50% Medium
1123 Lowest Common Ancestor of Deepest Leaves 68.50% Medium
1122 Relative Sort Array 67.90% Easy
1121 Divide Array Into Increasing Sequences $ 59.00% Hard
1120 Maximum Average Subtree $ 64.50% Medium
1119 Remove Vowels from a String $ 90.50% Easy
1118 Number of Days in a Month $ 57.30% Easy
1117 Building H2O ~ 53.10% Medium
1116 Print Zero Even Odd ~ 58.20% Medium
1115 Print FooBar Alternately ~ 59.00% Medium
1114 Print in Order ~ 67.60% Easy
1113 Reported Posts * $ 66.40% Medium
1112 Highest Grade For Each Student * $ 72.80% Medium
1111 Maximum Nesting Depth of Two Valid Parentheses Strings 72.70% Medium
1110 Delete Nodes And Return Forest 68.00% Medium
1109 Corporate Flight Bookings 54.30% Medium
1108 Defanging an IP Address 88.40% Easy
1107 New Users Daily Count * $ 46.10% Medium
1106 Parsing A Boolean Expression 59.50% Hard
1105 Filling Bookcase Shelves 57.50% Medium
1104 Path In Zigzag Labelled Binary Tree 73.50% Medium
1103 Distribute Candies to People 63.40% Easy
1102 Path With Maximum Minimum Value $ 51.00% Medium
1101 The Earliest Moment When Everyone Become Friends $ 67.80% Medium
1100 Find K-Length Substrings With No Repeated Characters $ 73.10% Medium
1099 Two Sum Less Than K $ 60.80% Easy
1098 Unpopular Books * $ 45.50% Medium
1097 Game Play Analysis V * $ 57.00% Hard
1096 Brace Expansion II 62.90% Hard
1095 Find in Mountain Array 36.10% Hard
1094 Car Pooling 59.70% Medium
1093 Statistics from a Large Sample 48.50% Medium
1092 Shortest Common Supersequence 53.40% Hard
1091 Shortest Path in Binary Matrix 40.30% Medium
1090 Largest Values From Labels 60.10% Medium
1089 Duplicate Zeros 51.60% Easy
1088 Confusing Number II 45.60% Hard
1087 Brace Expansion $ 63.20% Medium
1086 High Five $ 77.50% Easy
1085 Sum of Digits in the Minimum Number $ 75.10% Easy
1084 Sales Analysis III * $ 54.80% Easy
1083 Sales Analysis II * $ 50.80% Easy
1082 Sales Analysis I * $ 73.90% Easy
1081 Smallest Subsequence of Distinct Characters 53.60% Medium
1080 Insufficient Nodes in Root to Leaf Paths 50.10% Medium
1079 Letter Tile Possibilities 75.90% Medium
1078 Occurrences After Bigram 65.00% Easy
1077 Project Employees III * $ 78.00% Medium
1076 Project Employees II * $ 52.80% Easy
1075 Project Employees I * $ 66.20% Easy
1074 Number of Submatrices That Sum to Target 62.00% Hard
1073 Adding Two Negabinary Numbers 34.80% Medium
1072 Flip Columns For Maximum Number of Equal Rows 61.60% Medium
1071 Greatest Common Divisor of Strings 51.50% Easy
1070 Product Sales Analysis III * $ 49.90% Medium
1069 Product Sales Analysis II * $ 83.30% Easy
1068 Product Sales Analysis I * $ 82.10% Easy
1067 Digit Count in Range $ 41.60% Hard
1066 Campus Bikes II $ 54.20% Medium
1065 Index Pairs of a String $ 61.00% Easy
1064 Fixed Point $ 64.90% Easy
1063 Number of Valid Subarrays $ 72.10% Hard
1062 Longest Repeating Substring $ 58.40% Medium
1061 Lexicographically Smallest Equivalent String $ 66.90% Medium
1060 Missing Element in Sorted Array $ 54.80% Medium
1059 All Paths from Source Lead to Destination $ 43.00% Medium
1058 Minimize Rounding Error to Meet Target $ 43.70% Medium
1057 Campus Bikes $ 57.80% Medium
1056 Confusing Number $ 47.00% Easy
1055 Shortest Way to Form String $ 57.20% Medium
1054 Distant Barcodes 44.20% Medium
1053 Previous Permutation With One Swap 51.20% Medium
1052 Grumpy Bookstore Owner 55.90% Medium
1051 Height Checker 72.10% Easy
1050 Actors and Directors Who Cooperated At Least Three Times * $ 72.40% Easy
1049 Last Stone Weight II 45.40% Medium
1048 Longest String Chain 55.50% Medium
1047 Remove All Adjacent Duplicates In String 70.90% Easy
1046 Last Stone Weight 62.40% Easy
1045 Customers Who Bought All Products * $ 68.40% Medium
1044 Longest Duplicate Substring 31.40% Hard
1043 Partition Array for Maximum Sum 67.10% Medium
1042 Flower Planting With No Adjacent 48.80% Medium
1041 Robot Bounded In Circle 55.00% Medium
1040 Moving Stones Until Consecutive II 54.10% Medium
1039 Minimum Score Triangulation of Polygon 50.10% Medium
1038 Binary Search Tree to Greater Sum Tree 82.30% Medium
1037 Valid Boomerang 37.80% Easy
1036 Escape a Large Maze 34.50% Hard
1035 Uncrossed Lines 56.10% Medium
1034 Coloring A Border 45.70% Medium
1033 Moving Stones Until Consecutive 43.20% Easy
1032 Stream of Characters 48.60% Hard
1031 Maximum Sum of Two Non-Overlapping Subarrays 58.90% Medium
1030 Matrix Cells in Distance Order 66.90% Easy
1029 Two City Scheduling 57.90% Medium
1028 Recover a Tree From Preorder Traversal 70.90% Hard
1027 Longest Arithmetic Subsequence 49.80% Medium
1026 Maximum Difference Between Node and Ancestor 69.40% Medium
1025 Divisor Game 66.20% Easy
1024 Video Stitching 49.00% Medium
1023 Camelcase Matching 57.40% Medium
1022 Sum of Root To Leaf Binary Numbers 71.50% Easy
1021 Remove Outermost Parentheses 79.00% Easy
1020 Number of Enclaves 58.90% Medium
1019 Next Greater Node In Linked List 58.20% Medium
1018 Binary Prefix Divisible By 5 47.80% Easy
1017 Convert to Base -2 59.60% Medium
1016 Binary String With Substrings Representing 1 To N 58.90% Medium
1015 Smallest Integer Divisible by K 41.80% Medium
1014 Best Sightseeing Pair 52.90% Medium
1013 Partition Array Into Three Parts With Equal Sum 49.10% Easy
1012 Numbers With Repeated Digits 37.80% Hard
1011 Capacity To Ship Packages Within D Days 59.70% Medium
1010 Pairs of Songs With Total Durations Divisible by 60 50.10% Medium
1009 Complement of Base 10 Integer 61.50% Easy
1008 Construct Binary Search Tree from Preorder Traversal 78.80% Medium
1007 Minimum Domino Rotations For Equal Row 50.90% Medium
1006 Clumsy Factorial 53.70% Medium
1005 Maximize Sum Of Array After K Negations 52.40% Easy
1004 Max Consecutive Ones III 60.50% Medium
1003 Check If Word Is Valid After Substitutions 56.10% Medium
1002 Find Common Characters 68.10% Easy
1001 Grid Illumination 36.60% Hard
1000 Minimum Cost to Merge Stones 40.40% Hard
999 Available Captures for Rook 66.80% Easy
998 Maximum Binary Tree II 63.70% Medium
997 Find the Town Judge 49.80% Easy
996 Number of Squareful Arrays 48.00% Hard
995 Minimum Number of K Consecutive Bit Flips 49.60% Hard
994 Rotting Oranges 49.60% Medium
993 Cousins in Binary Tree 52.20% Easy
992 Subarrays with K Different Integers 50.40% Hard
991 Broken Calculator 46.40% Medium
990 Satisfiability of Equality Equations 46.50% Medium
989 Add to Array-Form of Integer 44.70% Easy
988 Smallest String Starting From Leaf 46.60% Medium
987 Vertical Order Traversal of a Binary Tree 37.60% Medium
986 Interval List Intersections 68.10% Medium
985 Sum of Even Numbers After Queries 60.70% Easy
984 String Without AAA or BBB 38.50% Medium
983 Minimum Cost For Tickets 62.60% Medium
982 Triples with Bitwise AND Equal To Zero 56.10% Hard
981 Time Based Key-Value Store 54.00% Medium
980 Unique Paths III 77.10% Hard
979 Distribute Coins in Binary Tree 69.30% Medium
978 Longest Turbulent Subarray 46.60% Medium
977 Squares of a Sorted Array 72.40% Easy
976 Largest Perimeter Triangle 58.50% Easy
975 Odd Even Jump 41.70% Hard
974 Subarray Sums Divisible by K 50.30% Medium
973 K Closest Points to Origin 64.40% Medium
972 Equal Rational Numbers 41.80% Hard
971 Flip Binary Tree To Match Preorder Traversal 46.10% Medium
970 Powerful Integers 39.90% Easy
969 Pancake Sorting 68.40% Medium
968 Binary Tree Cameras 38.40% Hard
967 Numbers With Same Consecutive Differences 44.30% Medium
966 Vowel Spellchecker 47.70% Medium
965 Univalued Binary Tree 67.60% Easy
964 Least Operators to Express Number 44.70% Hard
963 Minimum Area Rectangle II 51.60% Medium
962 Maximum Width Ramp 46.00% Medium
961 N-Repeated Element in Size 2N Array 74.20% Easy
960 Delete Columns to Make Sorted III 54.00% Hard
959 Regions Cut By Slashes 66.70% Medium
958 Check Completeness of a Binary Tree 52.30% Medium
957 Prison Cells After N Days 40.30% Medium
956 Tallest Billboard 39.70% Hard
955 Delete Columns to Make Sorted II 33.40% Medium
954 Array of Doubled Pairs 35.30% Medium
953 Verifying an Alien Dictionary 52.90% Easy
952 Largest Component Size by Common Factor 36.00% Hard
951 Flip Equivalent Binary Trees 65.50% Medium
950 Reveal Cards In Increasing Order 75.00% Medium
949 Largest Time for Given Digits 36.30% Medium
948 Bag of Tokens 46.20% Medium
947 Most Stones Removed with Same Row or Column 55.30% Medium
946 Validate Stack Sequences 63.10% Medium
945 Minimum Increment to Make Array Unique 46.50% Medium
944 Delete Columns to Make Sorted 70.90% Easy
943 Find the Shortest Superstring 43.20% Hard
942 DI String Match 73.20% Easy
941 Valid Mountain Array 32.20% Easy
940 Distinct Subsequences II 41.50% Hard
939 Minimum Area Rectangle 52.00% Medium
938 Range Sum of BST 79.90% Easy
937 Reorder Data in Log Files 53.70% Easy
936 Stamping The Sequence 38.10% Hard
935 Knight Dialer 43.80% Medium
934 Shortest Bridge 46.50% Medium
933 Number of Recent Calls 70.90% Easy
932 Beautiful Array 57.10% Medium
931 Minimum Falling Path Sum 61.30% Medium
930 Binary Subarrays With Sum 41.40% Medium
929 Unique Email Addresses 67.60% Easy
928 Minimize Malware Spread II 40.10% Hard
927 Three Equal Parts 32.80% Hard
926 Flip String to Monotone Increasing 51.60% Medium
925 Long Pressed Name 45.00% Easy
924 Minimize Malware Spread 41.70% Hard
923 3Sum With Multiplicity 35.30% Medium
922 Sort Array By Parity II 68.30% Easy
921 Minimum Add to Make Parentheses Valid 72.30% Medium
920 Number of Music Playlists 45.70% Hard
919 Complete Binary Tree Inserter 55.70% Medium
918 Maximum Sum Circular Subarray 33.20% Medium
917 Reverse Only Letters 56.30% Easy
916 Word Subsets 45.90% Medium
915 Partition Array into Disjoint Intervals 43.90% Medium
914 X of a Kind in a Deck of Cards 34.00% Easy
913 Cat and Mouse 29.90% Hard
912 Sort an Array 62.90% Medium
911 Online Election 48.30% Medium
910 Smallest Range II 24.90% Medium
909 Snakes and Ladders 36.10% Easy
908 Smallest Range I 65.00% Easy
907 Sum of Subarray Minimums 29.40% Medium
906 Super Palindromes 30.80% Hard
905 Sort Array By Parity 72.60% Easy
904 Fruit Into Baskets 41.70% Medium
903 Valid Permutations for DI Sequence 45.10% Hard
902 Numbers At Most N Given Digit Set 28.80% Hard
901 Online Stock Span 49.90% Medium
900 RLE Iterator 50.50% Medium
899 Orderly Queue 47.70% Hard
898 Bitwise ORs of Subarrays 34.70% Medium
897 Increasing Order Search Tree 65.20% Easy
896 Monotonic Array 55.40% Easy
895 Maximum Frequency Stack 56.70% Hard
894 All Possible Full Binary Trees 71.30% Medium
893 Groups of Special-Equivalent Strings 63.00% Easy
892 Surface Area of 3D Shapes 56.10% Easy
891 Sum of Subsequence Widths 29.20% Hard
890 Find and Replace Pattern 71.30% Medium
889 Construct Binary Tree from Preorder and Postorder Traversal 60.70% Medium
888 Fair Candy Swap 56.90% Easy
887 Super Egg Drop 24.90% Hard
886 Possible Bipartition 40.90% Medium
885 Spiral Matrix III 64.80% Medium
884 Uncommon Words from Two Sentences 60.90% Easy
883 Projection Area of 3D Shapes 65.90% Easy
882 Reachable Nodes In Subdivided Graph 38.20% Hard
881 Boats to Save People 44.10% Medium
880 Decoded String at Index 23.00% Medium
879 Profitable Schemes 36.20% Hard
878 Nth Magical Number 25.40% Hard
877 Stone Game 61.10% Medium
876 Middle of the Linked List 63.80% Easy
875 Koko Eating Bananas 45.90% Medium
874 Walking Robot Simulation 31.70% Easy
873 Length of Longest Fibonacci Subsequence 46.00% Medium
872 Leaf-Similar Trees 63.10% Easy
871 Minimum Number of Refueling Stops 28.70% Hard
870 Advantage Shuffle 42.20% Medium
869 Reordered Power of 2 50.60% Medium
868 Binary Gap 59.30% Easy
867 Transpose Matrix 63.90% Easy
866 Prime Palindrome 20.00% Medium
865 Smallest Subtree with all the Deepest Nodes 55.40% Medium
864 Shortest Path to Get All Keys 35.70% Hard
863 All Nodes Distance K in Binary Tree 46.80% Medium
862 Shortest Subarray with Sum at Least K 22.00% Hard
861 Score After Flipping Matrix 69.30% Medium
860 Lemonade Change 50.20% Easy
859 Buddy Strings 27.60% Easy
858 Mirror Reflection 52.00% Medium
857 Minimum Cost to Hire K Workers 47.30% Hard
856 Score of Parentheses 55.90% Medium
855 Exam Room 38.10% Medium
854 K-Similar Strings 33.20% Hard
853 Car Fleet 39.30% Medium
852 Peak Index in a Mountain Array 68.50% Easy
851 Loud and Rich 47.40% Medium
850 Rectangle Area II 44.00% Hard
849 Maximize Distance to Closest Person 40.40% Easy
848 Shifting Letters 39.90% Medium
847 Shortest Path Visiting All Nodes 45.90% Hard
846 Hand of Straights 48.60% Medium
845 Longest Mountain in Array 34.00% Medium
844 Backspace String Compare 45.50% Easy
843 Guess the Word 42.60% Hard
842 Split Array into Fibonacci Sequence 34.60% Medium
841 Keys and Rooms 59.70% Medium
840 Magic Squares In Grid 35.10% Easy
839 Similar String Groups 33.50% Hard
838 Push Dominoes 42.80% Medium
837 New 21 Game 29.50% Medium
836 Rectangle Overlap 45.50% Easy
835 Image Overlap 50.30% Medium
834 Sum of Distances in Tree 38.10% Hard
833 Find And Replace in String 44.50% Medium
832 Flipping an Image 71.30% Easy
831 Masking Personal Information 41.50% Medium
830 Positions of Large Groups 47.40% Easy
829 Consecutive Numbers Sum 32.10% Hard
828 Unique Letter String 38.10% Hard
827 Making A Large Island 42.10% Hard
826 Most Profit Assigning Work 34.70% Medium
825 Friends Of Appropriate Ages 35.00% Medium
824 Goat Latin 56.70% Easy
823 Binary Trees With Factors 31.50% Medium
822 Card Flipping Game 39.70% Medium
821 Shortest Distance to a Character 62.60% Easy
820 Short Encoding of Words 46.00% Medium
819 Most Common Word 41.50% Easy
818 Race Car 34.00% Hard
817 Linked List Components 51.90% Medium
816 Ambiguous Coordinates 42.40% Medium
815 Bus Routes 36.10% Hard
814 Binary Tree Pruning 68.30% Medium
813 Largest Sum of Averages 42.40% Medium
812 Largest Triangle Area 53.80% Easy
811 Subdomain Visit Count 61.50% Easy
810 Chalkboard XOR Game 41.90% Hard
809 Expressive Words 39.50% Medium
808 Soup Servings 33.60% Medium
807 Max Increase to Keep City Skyline 79.60% Medium
806 Number of Lines To Write String 62.10% Easy
805 Split Array With Same Average 21.00% Hard
804 Unique Morse Code Words 71.30% Easy
803 Bricks Falling When Hit 22.90% Hard
802 Find Eventual Safe States 39.20% Medium
801 Minimum Swaps To Make Sequences Increasing 31.00% Medium
800 Similar RGB Color $ 54.50% Easy
799 Champagne Tower 29.90% Medium
798 Smallest Rotation with Highest Score 34.30% Hard
797 All Paths From Source to Target 67.40% Medium
796 Rotate String 49.60% Easy
795 Number of Subarrays with Bounded Maximum 41.60% Medium
794 Valid Tic-Tac-Toe State 27.80% Medium
793 Preimage Size of Factorial Zeroes Function 40.80% Hard
792 Number of Matching Subsequences 37.30% Medium
791 Custom Sort String 59.40% Medium
790 Domino and Tromino Tiling 32.80% Medium
789 Escape The Ghosts 51.00% Medium
788 Rotated Digits 51.00% Easy
787 Cheapest Flights Within K Stops 29.90% Medium
786 K-th Smallest Prime Fraction 32.50% Hard
785 Is Graph Bipartite 38.50% Medium
784 Letter Case Permutation 53.00% Easy
783 Minimum Distance Between BST Nodes 47.80% Easy
782 Transform to Chessboard 37.50% Hard
781 Rabbits in Forest 49.80% Medium
780 Reaching Points 23.90% Hard
779 K-th Symbol in Grammar 37.30% Medium
778 Swim in Rising Water 44.60% Hard
777 Swap Adjacent in LR String 28.90% Medium
776 Split BST $ 49.90% Medium
775 Global and Local Inversions 33.60% Medium
774 Minimize Max Distance to Gas Station $ 32.80% Hard
773 Sliding Puzzle 46.50% Hard
772 Basic Calculator III $ 40.10% Hard
771 Jewels and Stones 81.90% Easy
770 Basic Calculator IV 42.10% Hard
769 Max Chunks To Make Sorted 48.00% Medium
768 Max Chunks To Make Sorted II 43.20% Hard
767 Reorganize String 36.50% Medium
766 Toeplitz Matrix 57.90% Easy
765 Couples Holding Hands 48.50% Hard
764 Largest Plus Sign 39.20% Medium
763 Partition Labels 64.10% Medium
762 Prime Number of Set Bits in Binary Representation 55.00% Easy
761 Special Binary String 41.00% Hard
760 Find Anagram Mappings $ 75.60% Easy
759 Employee Free Time $ 51.90% Hard
758 Bold Words in String $ 37.90% Easy
757 Set Intersection Size At Least Two 34.60% Hard
756 Pyramid Transition Matrix 45.50% Medium
755 Pour Water $ 34.00% Medium
754 Reach a Number 26.10% Medium
753 Cracking the Safe 39.70% Hard
752 Open the Lock 38.20% Medium
751 IP to CIDR $ 54.80% Easy
750 Number Of Corner Rectangles $ 51.00% Medium
749 Contain Virus 39.60% Hard
748 Shortest Completing Word 53.50% Medium
747 Largest Number At Least Twice of Others 42.60% Easy
746 Min Cost Climbing Stairs 43.60% Easy
745 Prefix and Suffix Search 24.50% Hard
744 Find Smallest Letter Greater Than Target 45.30% Easy
743 Network Delay Time 34.30% Medium
742 Closest Leaf in a Binary Tree $ 33.20% Medium
741 Cherry Pickup 22.50% Hard
740 Delete and Earn 42.60% Medium
739 Daily Temperatures 53.50% Medium
738 Monotone Increasing Digits 41.80% Medium
737 Sentence Similarity II $ 41.20% Medium
736 Parse Lisp Expression 42.00% Hard
735 Asteroid Collision 37.60% Medium
734 Sentence Similarity $ 38.60% Easy
733 Flood Fill 49.30% Easy
732 My Calendar III 51.30% Hard
731 My Calendar II 35.00% Medium
730 Count Different Palindromic Subsequences 32.40% Hard
729 My Calendar I 40.10% Medium
728 Self Dividing Numbers 68.50% Easy
727 Minimum Window Subsequence 30.10% Hard
726 Number of Atoms 45.00% Hard
725 Split Linked List in Parts 50.00% Medium
724 Find Pivot Index 41.00% Easy
723 Candy Crush $ 56.10% Medium
722 Remove Comments 26.30% Medium
721 Accounts Merge 29.60% Medium
720 Longest Word in Dictionary 40.60% Easy
719 Find K-th Smallest Pair Distance 26.10% Hard
718 Maximum Length of Repeated Subarray 39.90% Medium
717 1-bit and 2-bit Characters 50.90% Easy
716 Max Stack $ 35.50% Hard
715 Range Module 31.00% Hard
714 Best Time to Buy and Sell Stock with Transaction Fee 41.60% Medium
713 Subarray Product Less Than K 32.90% Medium
712 Minimum ASCII Delete Sum for Two Strings 50.90% Medium
711 Number of Distinct Islands II $ 39.40% Hard
710 Random Pick with Blacklist 29.80% Hard
709 To Lower Case 74.50% Easy
708 Insert into a Cyclic Sorted List $ 25.50% Medium
707 Design Linked List 19.10% Easy
706 Design HashMap 49.10% Easy
705 Design HashSet 43.60% Easy
704 Binary Search 40.10% Easy
703 Kth Largest Element in a Stream 39.60% Easy
702 Search in a Sorted Array of Unknown Size 43.00% Medium
701 Insert into a Binary Search Tree 67.90% Medium
700 Search in a Binary Search Tree 62.70% Easy
699 Falling Squares 36.80% Hard
698 Partition to K Equal Sum Subsets 35.60% Medium
697 Degree of an Array 47.90% Easy
696 Count Binary Substrings 51.90% Easy
695 Max Area of Island 53.30% Easy
694 Number of Distinct Islands $ 43.90% Medium
693 Binary Number with Alternating Bits 54.20% Easy
692 Top K Frequent Words 41.70% Medium
691 Stickers to Spell Word 32.30% Hard
690 Employee Importance 53.60% Easy
689 Maximum Sum of 3 Non-Overlapping Subarrays 41.70% Hard
688 Knight Probability in Chessboard 38.90% Medium
687 Longest Univalue Path 32.90% Easy
686 Repeated String Match 31.70% Easy
685 Redundant Connection II 28.40% Hard
684 Redundant Connection 37.40% Medium
683 K Empty Slots 37.00% Hard
682 Baseball Game 58.60% Easy
681 Next Closest Time $ 43.00% Medium
680 Valid Palindrome II 31.10% Easy
679 24 Game 38.30% Hard
678 Valid Parenthesis String 28.30% Medium
677 Map Sum Pairs 53.80% Medium
676 Implement Magic Dictionary 50.70% Medium
675 Cut Off Trees for Golf Event 27.20% Hard
674 Longest Continuous Increasing Subsequence 43.20% Easy
673 Number of Longest Increasing Subsequence 30.80% Medium
672 Bulb Switcher II 47.60% Medium
671 Second Minimum Node In a Binary Tree 42.30% Easy
670 Maximum Swap 38.40% Medium
669 Trim a Binary Search Tree 58.70% Easy
668 Kth Smallest Number in Multiplication Table 36.30% Hard
667 Beautiful Arrangement II 51.50% Medium
666 Path Sum IV $ 48.90% Medium
665 Non-decreasing Array 21.20% Easy
664 Strange Printer 31.10% Hard
663 Equal Tree Partition $ 36.70% Medium
662 Maximum Width of Binary Tree 37.00% Medium
661 Image Smoother 46.60% Easy
660 Remove 9 $ 46.10% Hard
659 Split Array into Consecutive Subsequences 39.40% Medium
658 Find K Closest Elements 36.30% Medium
657 Judge Route Circle 69.30% Easy
656 Coin Path $ 24.60% Hard
655 Print Binary Tree 50.80% Medium
654 Maximum Binary Tree 70.80% Medium
653 Two Sum IV - Input is a BST 50.60% Easy
652 Find Duplicate Subtrees 33.20% Medium
651 4 Keys Keyboard $ 46.70% Medium
650 2 Keys Keyboard 42.70% Medium
649 Dota2 Senate 35.60% Medium
648 Replace Words 48.40% Medium
647 Palindromic Substrings 55.70% Medium
646 Maximum Length of Pair Chain 47.40% Medium
645 Set Mismatch 40.40% Easy
644 Maximum Average Subarray II $ 20.30% Hard
643 Maximum Average Subarray I 38.40% Easy
642 Design Search Autocomplete System $ 29.40% Hard
641 Design Circular Deque 48.00% Medium
640 Solve the Equation 39.00% Medium
639 Decode Ways II 22.80% Hard
638 Shopping Offers 41.50% Medium
637 Average of Levels in Binary Tree 55.60% Easy
636 Exclusive Time of Functions 40.30% Medium
635 Design Log Storage System $ 47.70% Medium
634 Find the Derangement of An Array $ 32.70% Medium
633 Sum of Square Numbers 31.60% Easy
632 Smallest Range 42.80% Hard
631 Design Excel Sum Formula $ 25.60% Hard
630 Course Schedule III 20.50% Medium
629 K Inverse Pairs Array 23.90% Hard
628 Maximum Product of Three Numbers 45.40% Easy
627 Swap Salary 67.40% Easy
626 Exchange Seats 49.60% Medium
625 Minimum Factorization 29.50% Medium
624 Maximum Distance in Arrays $ 32.70% Easy
623 Add One Row to Tree 48.70% Medium
622 Design Circular Queue 36.60% Medium
621 Task Scheduler 41.40% Medium
620 Not Boring Movies 59.80% Easy
619 Biggest Single Number $ 36.60% Easy
618 Students Report By Geography $ 40.40% Hard
617 Merge Two Binary Trees 69.60% Easy
616 Add Bold Tag in String $ 37.30% Medium
615 Average Salary: Departments VS Company $ 33.00% Hard
614 Second Degree Follower $ 22.70% Medium
613 Shortest Distance in a Line $ 70.60% Easy
612 Shortest Distance in a Plane $ 51.00% Medium
611 Valid Triangle Number 38.90% Medium
610 Triangle Judgement $ 59.10% Easy
609 Find Duplicate File in System 52.50% Medium
608 Tree Node $ 54.60% Medium
607 Sales Person $ 51.10% Easy
606 Construct String from Binary Tree 51.80% Easy
605 Can Place Flowers 30.00% Easy
604 Design Compressed String Iterator $ 31.60% Easy
603 Consecutive Available Seats $ 54.50% Easy
602 Friend Requests II: Who Has the Most Friends $ 42.50% Medium
601 Human Traffic of Stadium 33.80% Hard
600 Non-negative Integers without Consecutive Ones 27.40% Hard
599 Minimum Index Sum of Two Lists 48.00% Easy
598 Range Addition II 48.30% Easy
597 Friend Requests I: Overall Acceptance Rate $ 38.40% Easy
596 Classes More Than 5 Students 33.40% Easy
595 Big Countries 72.00% Easy
594 Longest Harmonious Subsequence 40.00% Easy
593 Valid Square 39.20% Medium
592 Fraction Addition and Subtraction 47.00% Medium
591 Tag Validator 26.40% Hard
590 N-ary Tree Postorder Traversal 63.00% Easy
589 N-ary Tree Preorder Traversal 63.10% Easy
588 Design In-Memory File System $ 32.40% Hard
587 Erect the Fence 29.90% Hard
586 Customer Placing the Largest Number of Orders $ 62.50% Easy
585 Investments in 2016 $ 44.60% Medium
584 Find Customer Referee $ 63.60% Easy
583 Delete Operation for Two Strings 44.00% Medium
582 Kill Process $ 47.70% Medium
581 Shortest Unsorted Continuous Subarray 30.10% Easy
580 Count Student Number in Departments $ 40.00% Medium
579 Find Cumulative Salary of an Employee $ 30.90% Hard
578 Get Highest Answer Rate Question $ 33.10% Medium
577 Employee Bonus $ 53.80% Easy
576 Out of Boundary Paths 33.10% Medium
575 Distribute Candies 59.50% Easy
574 Winning Candidate $ 34.50% Medium
573 Squirrel Simulation $ 51.00% Medium
572 Subtree of Another Tree 41.00% Easy
571 Find Median Given Frequency of Numbers $ 44.90% Hard
570 Managers with at Least 5 Direct Reports $ 59.00% Medium
569 Median Employee Salary $ 41.60% Hard
568 Maximum Vacation Days $ 39.90% Hard
567 Permutation in String 36.30% Medium
566 Reshape the Matrix 59.80% Easy
565 Array Nesting 50.00% Medium
564 Find the Closest Palindrome 16.60% Hard
563 Binary Tree Tilt 47.00% Easy
562 Longest Line of Consecutive One in Matrix $ 38.80% Medium
561 Array Partition I 69.80% Easy
560 Subarray Sum Equals K 41.80% Medium
559 Maximum Depth of N-ary Tree 62.10% Easy
558 Quad Tree Intersection 36.40% Easy
557 Reverse Words in a String III 61.20% Easy
556 Next Greater Element III 27.70% Medium
555 Split Concatenated Strings $ 30.00% Medium
554 Brick Wall 42.70% Medium
553 Optimal Division 55.20% Medium
552 Student Attendance Record II 28.50% Hard
551 Student Attendance Record I 43.90% Easy
549 Binary Tree Longest Consecutive Sequence II $ 38.10% Medium
548 Split Array with Equal Sum $ 30.80% Medium
547 Friend Circles 49.00% Medium
546 Remove Boxes 29.60% Hard
545 Boundary of Binary Tree $ 29.10% Medium
544 Output Contest Matches $ 73.20% Medium
543 Diameter of Binary Tree 42.70% Easy
542 01 Matrix 32.50% Medium
541 Reverse String II 44.40% Easy
540 Single Element in a Sorted Array 55.90% Medium
539 Minimum Time Difference 44.70% Medium
538 Convert BST to Greater Tree 52.70% Medium
537 Complex Number Multiplication 65.90% Medium
536 Construct Binary Tree from String $ 36.30% Medium
535 Encode and Decode TinyURL 76.10% Medium
534 Game Play Analysis III 67.10% Medium
533 Lonely Pixel II $ 38.90% Medium
532 K-diff Pairs in an Array 27.20% Easy
531 Lonely Pixel I $ 50.20% Medium
530 Minimum Absolute Difference in BST 48.00% Easy
529 Minesweeper 52.00% Medium
528 Random Pick with Weight 41.80% Medium
527 Word Abbreviation $ 33.90% Hard
526 Beautiful Arrangement 53.50% Medium
525 Contiguous Array 34.40% Medium
524 Longest Word in Dictionary through Deleting 40.20% Medium
523 Continuous Subarray Sum 21.30% Medium
522 Longest Uncommon Subsequence II 28.10% Medium
521 Longest Uncommon Subsequence I 50.70% Easy
520 Detect Capital 54.20% Easy
519 Random Flip Matrix 32.20% Medium
518 Coin Change 2 33.20% Medium
517 Super Washing Machines 34.60% Hard
516 Longest Palindromic Subsequence 42.00% Medium
515 Find Largest Value in Each Tree Row 52.70% Medium
514 Freedom Trail 27.20% Hard
513 Find Bottom Left Tree Value 55.60% Medium
510 Inorder Successor in BST II $ 56.20% Medium
509 Fibonacci Number 66.40% Easy
508 Most Frequent Subtree Sum 52.00% Medium
507 Perfect Number 32.70% Easy
506 Relative Ranks 48.50% Easy
505 The Maze II 34.80% Medium
504 Base 7 46.40% Easy
503 Next Greater Element II 46.20% Medium
502 IPO 32.40% Hard
501 Find Mode in Binary Search Tree 39.40% Easy
500 Keyboard Row 60.20% Easy
499 The Maze III 32.00% Hard
498 Diagonal Traverse 46.20% Medium
497 Random Point in Non-overlapping Rectangles 33.20% Medium
496 Next Greater Element I 58.80% Easy
495 Teemo Attacking 51.90% Medium
494 Target Sum 44.40% Medium
493 Reverse Pairs 17.10% Hard
492 Construct the Rectangle 49.70% Easy
491 Increasing Subsequences 39.30% Medium
490 The Maze 42.80% Medium
489 Robot Room Cleaner 57.10% Hard
488 Zuma Game 38.00% Hard
487 Max Consecutive Ones II $ 42.70% Medium
486 Predict the Winner 43.60% Medium
485 Max Consecutive Ones 55.30% Easy
484 Find Permutation $ 50.50% Medium
483 Smallest Good Base 30.60% Hard
482 License Key Formatting 41.20% Medium
481 Magical String 46.20% Medium
480 Sliding Window Median 31.00% Hard
479 Largest Palindrome Product 23.90% Easy
478 Generate Random Point in a Circle 33.80% Medium
477 Total Hamming Distance 44.10% Medium
476 Number Complement 61.20% Easy
475 Heaters 30.20% Easy
474 Ones and Zeroes 34.90% Medium
473 Matchsticks to Square 31.80% Medium
472 Concatenated Words 29.20% Hard
471 Encode String with Shortest Length $ 43.50% Hard
470 Implement Rand10() Using Rand7() 43.00% Medium
469 Convex Polygon $ 27.20% Medium
468 Validate IP Address 22.10% Medium
467 Unique Substrings in Wraparound String 29.90% Medium
466 Count The Repetitions 24.20% Hard
465 Optimal Account Balancing $ 29.20% Hard
464 Can I Win 22.20% Medium
463 Island Perimeter 56.70% Easy
462 Minimum Moves to Equal Array Elements II 50.90% Medium
461 Hamming Distance 73.20% Easy
460 LFU Cache 18.30% Hard
459 Repeated Substring Pattern 39.70% Easy
458 Poor Pigs 40.70% Easy
457 Circular Array Loop 20.60% Medium
456 132 Pattern 28.00% Medium
455 Assign Cookies 48.10% Easy
454 4Sum II 42.80% Medium
453 Minimum Moves to Equal Array Elements 46.30% Easy
452 Minimum Number of Arrows to Burst Balloons 42.10% Medium
451 Sort Characters By Frequency 50.90% Medium
450 Delete Node in a BST 34.50% Medium
449 Serialize and Deserialize BST 41.20% Medium
448 Find All Numbers Disappeared in an Array 58.30% Easy
447 Number of Boomerangs 42.20% Easy
446 Arithmetic Slices II - Subsequence 22.30% Hard
445 Add Two Numbers II 45.40% Medium
444 Sequence Reconstruction $ 20.50% Medium
443 String Compression 35.80% Easy
442 Find All Duplicates in an Array 46.40% Medium
441 Arranging Coins 36.20% Easy
440 K-th Smallest in Lexicographical Order 21.50% Hard
439 Ternary Expression Parser $ 49.40% Medium
438 Find All Anagrams in a String 33.50% Easy
437 Path Sum III 38.60% Easy
436 Find Right Interval 42.30% Medium
435 Non-overlapping Intervals 39.80% Medium
434 Number of Segments in a String 38.40% Easy
433 Minimum Genetic Mutation 33.50% Medium
432 All O`one Data Structure 28.30% Hard
431 Encode N-ary Tree to Binary Tree $ 53.70% Hard
430 Flatten a Multilevel Doubly Linked List 36.20% Medium
429 N-ary Tree Level Order Traversal 55.80% Easy
428 Serialize and Deserialize N-ary Tree $ 48.10% Hard
427 Construct Quad Tree 49.20% Easy
426 Convert Binary Search Tree to Sorted Doubly Linked List 43.30% Medium
425 Word Squares $ 40.10% Hard
424 Longest Repeating Character Replacement 38.10% Medium
423 Reconstruct Original Digits from English 40.80% Medium
422 Valid Word Square $ 36.40% Easy
421 Maximum XOR of Two Numbers in an Array 36.40% Medium
420 Strong Password Checker 22.10% Hard
419 Battleships in a Board 59.30% Medium
418 Sentence Screen Fitting $ 25.10% Medium
417 Pacific Atlantic Water Flow 31.10% Medium
416 Partition Equal Subset Sum 36.80% Medium
415 Add Strings 41.50% Easy
414 Third Maximum Number 26.50% Easy
413 Arithmetic Slices 53.50% Medium
412 Fizz Buzz 57.50% Easy
411 Minimum Unique Word Abbreviation $ 25.60% Hard
410 Split Array Largest Sum 25.00% Hard
409 Longest Palindrome 44.90% Easy
408 Valid Word Abbreviation $ 26.20% Easy
407 Trapping Rain Water II 33.10% Hard
406 Queue Reconstruction by Height 54.10% Medium
405 Convert a Number to Hexadecimal 41.80% Easy
404 Sum of Left Leaves 46.20% Easy
403 Frog Jump 31.70% Hard
402 Remove K Digits 25.90% Medium
401 Binary Watch 43.00% Easy
400 Nth Digit 30.70% Easy
399 Evaluate Division 34.60% Medium
398 Random Pick Index 30.40% Medium
397 Integer Replacement 25.90% Easy
396 Rotate Function 28.40% Easy
395 Longest Substring with At Least K Repeating Characters 32.50% Medium
394 Decode String 38.60% Medium
393 UTF-8 Validation 33.00% Medium
392 Is Subsequence 44.10% Medium
391 Perfect Rectangle 13.30% Hard
390 Elimination Game 12.50% Medium
389 Find the Difference 49.90% Easy
388 Longest Absolute File Path 28.30% Medium
387 First Unique Character in a String 43.10% Easy
386 Lexicographical Numbers 31.00% Medium
385 Mini Parser 26.90% Medium
384 Shuffle an Array 45.50% Medium
383 Ransom Note 44.60% Easy
382 Linked List Random Node 48.60% Medium
381 Insert Delete GetRandom O(1) - Duplicates allowed 30.90% Hard
380 Insert Delete GetRandom O(1) 33.80% Medium
379 Design Phone Directory $ 25.80% Medium
378 Kth Smallest Element in a Sorted Matrix 40.20% Medium
377 Combination Sum IV 37.50% Medium
376 Wiggle Subsequence 35.80% Medium
375 Guess Number Higher or Lower II 28.30% Medium
374 Guess Number Higher or Lower 31.70% Easy
373 Find K Pairs with Smallest Sums 25.50% Medium
372 Super Pow 30.10% Medium
371 Sum of Two Integers 54.00% Easy
370 Range Addition $ 49.90% Medium
369 Plus One Linked List $ 50.10% Medium
368 Largest Divisible Subset 32.00% Medium
367 Valid Perfect Square 36.60% Medium
366 Find Leaves of Binary Tree $ 53.60% Medium
365 Water and Jug Problem 20.10% Medium
364 Nested List Weight Sum II $ 47.20% Medium
363 Max Sum of Rectangle No Larger Than K 27.00% Hard
362 Design Hit Counter $ 48.30% Medium
361 Bomb Enemy $ 32.60% Medium
360 Sort Transformed Array $ 40.80% Medium
359 Logger Rate Limiter $ 56.00% Easy
358 Rearrange String k Distance Apart $ 26.90% Hard
357 Count Numbers with Unique Digits 42.50% Medium
356 Line Reflection $ 28.70% Medium
355 Design Twitter 22.20% Medium
354 Russian Doll Envelopes 27.10% Hard
353 Design Snake Game $ 19.80% Medium
352 Data Stream as Disjoint Intervals 34.40% Hard
351 Android Unlock Patterns $ 32.00% Medium
350 Intersection of Two Arrays II 41.50% Easy
349 Intersection of Two Arrays 47.80% Easy
348 Design Tic-Tac-Toe $ 45.60% Medium
347 Top K Frequent Elements 44.50% Medium
346 Moving Average from Data Stream $ 69.20% Easy
345 Reverse Vowels of a String 35.30% Easy
344 Reverse String 58.90% Easy
343 Integer Break 43.60% Medium
342 Power of Four 34.10% Easy
341 Flatten Nested List Iterator $ 18.70% Medium
340 Longest Substring with At Most K Distinct Characters $ 36.30% Hard
339 Nested List Weight Sum $ 54.60% Easy
338 Counting Bits 55.40% Medium
337 House Robber III 37.00% Medium
336 Palindrome Pairs 18.70% Hard
335 Self Crossing 18.00% Medium
334 Increasing Triplet Subsequence 33.20% Medium
333 Largest BST Subtree $ 26.50% Medium
332 Reconstruct Itinerary 23.40% Medium
331 Verify Preorder Serialization of a Binary Tree 31.50% Medium
330 Patching Array 28.80% Medium
329 Longest Increasing Path in a Matrix 29.50% Medium
328 Odd Even Linked List 37.80% Easy
327 Count of Range Sum 24.30% Hard
326 Power of Three 35.30% Easy
325 Maximum Size Subarray Sum Equals k $ 39.60% Easy
324 Wiggle Sort II 20.10% Medium
323 Number of Connected Components in an Undirected Graph $ 43.30% Medium
322 Coin Change 24.90% Medium
321 Create Maximum Number 19.10% Hard
320 Generalized Abbreviation $ 40.40% Medium
319 Bulb Switcher 39.20% Medium
318 Maximum Product of Word Lengths 38.70% Medium
317 Shortest Distance from All Buildings $ 29.10% Hard
316 Remove Duplicate Letters 23.00% Medium
315 Count of Smaller Numbers After Self 28.20% Hard
314 Binary Tree Vertical Order Traversal $ 30.10% Medium
313 Super Ugly Number 31.20% Medium
312 Burst Balloons 24.50% Medium
311 Sparse Matrix Multiplication $ 40.00% Medium
310 Minimum Height Trees 20.20% Medium
309 Best Time to Buy and Sell Stock with Cooldown 32.60% Medium
308 Range Sum Query 2D - Mutable $ 20.30% Hard
307 Range Sum Query - Mutable 14.50% Medium
306 Additive Number 23.30% Medium
305 Number of Islands II $ 26.70% Hard
304 Range Sum Query 2D - Immutable 21.50% Medium
303 Range Sum Query - Immutable 25.70% Easy
302 Smallest Rectangle Enclosing Black Pixels $ 36.70% Hard
301 Remove Invalid Parentheses 27.00% Hard
300 Longest Increasing Subsequence 31.50% Medium
299 Bulls and Cows 23.80% Easy
298 Binary Tree Longest Consecutive Sequence $ 32.20% Medium
297 Serialize and Deserialize Binary Tree 23.80% Medium
296 Best Meeting Point $ 41.40% Hard
295 Find Median from Data Stream 18.60% Hard
294 Flip Game II $ 38.10% Medium
293 Flip Game $ 47.60% Easy
292 Nim Game 49.50% Easy
291 Word Pattern II $ 31.50% Hard
290 Word Pattern 26.50% Easy
289 Game of Life 32.60% Medium
288 Unique Word Abbreviation $ 16.80% Easy
287 Find the Duplicate Number 32.80% Hard
286 Walls and Gates $ 29.90% Medium
285 Inorder Successor in BST $ 32.50% Medium
284 Peeking Iterator 31.00% Medium
283 Move Zeroes 41.40% Easy
282 Expression Add Operators 18.90% Hard
281 Zigzag Iterator $ 37.20% Medium
280 Wiggle Sort $ 43.30% Medium
279 Perfect Squares 28.80% Medium
278 First Bad Version 19.80% Easy
277 Find the Celebrity $ 31.70% Medium
276 Paint Fence $ 25.00% Easy
275 H-Index II 31.40% Medium
274 H-Index 25.30% Medium
273 Integer to English Words 15.50% Medium
272 Closest Binary Search Tree Value II $ 26.90% Hard
271 Encode and Decode Strings $ 25.40% Medium
270 Closest Binary Search Tree Value $ 29.40% Easy
269 Alien Dictionary $ 16.50% Hard
268 Missing Number 34.70% Medium
267 Palindrome Permutation II $ 22.50% Medium
266 Palindrome Permutation $ 45.80% Easy
265 Paint House II $ 30.00% Hard
264 Ugly Number II 21.60% Medium
263 Ugly Number 32.60% Easy
262 Trips and Users * 16.10% Hard
261 Graph Valid Tree $ 25.40% Medium
260 Single Number III 37.60% Medium
259 3Sum Smaller $ 34.20% Medium
258 Add Digits 46.50% Easy
257 Binary Tree Paths 21.90% Easy
256 Paint House $ 38.40% Medium
255 Verify Preorder Sequence in Binary Search Tree $ 32.30% Medium
254 Factor Combinations $ 29.00% Medium
253 Meeting Rooms II $ 28.80% Medium
252 Meeting Rooms $ 35.40% Easy
251 Flatten 2D Vector $ 28.30% Medium
250 Count Univalue Subtrees $ 32.70% Medium
249 Group Shifted Strings $ 25.20% Easy
248 Strobogrammatic Number III $ 21.80% Hard
247 Strobogrammatic Number II $ 26.70% Medium
246 Strobogrammatic Number $ 31.60% Easy
245 Shortest Word Distance III $ 43.20% Medium
244 Shortest Word Distance II $ 33.70% Medium
243 Shortest Word Distance $ 41.80% Easy
242 Valid Anagram 39.30% Easy
241 Different Ways to Add Parentheses 27.10% Medium
240 Search a 2D Matrix II 31.40% Medium
239 Sliding Window Maximum 24.30% Hard
238 Product of Array Except Self 42.40% Medium
237 Delete Node in a Linked List 50.90% Easy
236 Lowest Common Ancestor of a Binary Tree 28.30% Medium
235 Lowest Common Ancestor of a Binary Search Tree 38.90% Medium
234 Palindrome Linked List 23.80% Easy
233 Number of Digit One 16.10% Medium
232 Implement Queue using Stacks 37.40% Easy
231 Power of Two 31.30% Easy
230 Kth Smallest Element in a BST 30.50% Medium
229 Majority Element II 30.50% Medium
228 Summary Ranges 21.10% Easy
227 Basic Calculator II 18.00% Medium
226 Invert Binary Tree 35.40% Easy
225 Implement Stack using Queues 29.60% Medium
224 Basic Calculator 15.80% Medium
223 Rectangle Area 25.60% Easy
222 Count Complete Tree Nodes 19.40% Medium
221 Maximal Square 20.30% Medium
220 Contains Duplicate III 15.30% Medium
219 Contains Duplicate II 25.60% Easy
218 The Skyline Problem 16.20% Hard
217 Contains Duplicate 35.90% Easy
216 Combination Sum III 27.70% Medium
215 Kth Largest Element in an Array 27.30% Medium
214 Shortest Palindrome 16.80% Hard
213 House Robber II 26.30% Medium
212 Word Search II 15.00% Hard
211 Add and Search Word - Data structure design 20.70% Medium
210 Course Schedule II 19.30% Medium
209 Minimum Size Subarray Sum 22.90% Medium
208 Implement Trie (Prefix Tree) 24.80% Medium
207 Course Schedule 21.70% Medium
206 Reverse Linked List 31.50% Easy
205 Isomorphic Strings 24.20% Easy
204 Count Primes 19.00% Easy
203 Remove Linked List Elements 25.80% Easy
202 Happy Number 31.40% Easy
201 Bitwise AND of Numbers Range 23.30% Medium
200 Number of Islands 21.90% Medium
199 Binary Tree Right Side View 27.10% Medium
198 House Robber 28.80% Easy
197 Rising Temperature * 25.90% Easy
196 Delete Duplicate Emails * 19.00% Easy
195 Tenth Line # 32.20% Easy
194 Transpose File # 21.40% Medium
193 Valid Phone Numbers # 24.40% Easy
192 Word Frequency # 26.10% Medium
191 Number of 1 Bits 37.30% Easy
190 Reverse Bits 28.40% Easy
189 Rotate Array 17.80% Easy
188 Best Time to Buy and Sell Stock IV 17.30% Hard
187 Repeated DNA Sequences 19.50% Medium
186 Reverse Words in a String II $ 31.10% Medium
185 Department Top Three Salaries * 15.20% Hard
184 Department Highest Salary * 19.00% Medium
183 Customers Who Never Order * 33.70% Easy
182 Duplicate Emails * 38.10% Easy
181 Employees Earning More Than Their Managers * 41.00% Easy
180 Consecutive Numbers * 26.60% Medium
179 Largest Number 15.70% Medium
178 Rank Scores * 24.60% Medium
177 Nth Highest Salary * 16.30% Medium
176 Second Highest Salary * 25.00% Easy
175 Combine Two Tables * 34.40% Easy
174 Dungeon Game 17.70% Hard
173 Binary Search Tree Iterator 29.30% Medium
172 Factorial Trailing Zeroes 28.40% Easy
171 Excel Sheet Column Number 36.50% Easy
170 Two Sum III - Data structure design $ 24.70% Easy
169 Majority Element 35.00% Easy
168 Excel Sheet Column Title 18.10% Easy
167 Two Sum II - Input array is sorted $ 43.30% Medium
166 Fraction to Recurring Decimal 12.70% Medium
165 Compare Version Numbers 15.20% Easy
164 Maximum Gap 24.40% Hard
163 Missing Ranges $ 24.10% Medium
162 Find Peak Element 31.50% Medium
161 One Edit Distance $ 24.00% Medium
160 Intersection of Two Linked Lists 28.70% Easy
159 Longest Substring with At Most Two Distinct Characters $ 30.20% Hard
158 Read N Characters Given Read4 II - Call multiple times $ 22.30% Hard
157 Read N Characters Given Read4 $ 29.80% Easy
156 Binary Tree Upside Down $ 34.30% Medium
155 Min Stack 18.50% Easy
154 Find Minimum in Rotated Sorted Array II 31.90% Hard
153 Find Minimum in Rotated Sorted Array 33.30% Medium
152 Maximum Product Subarray 19.40% Medium
151 Reverse Words in a String 15.10% Medium
150 Evaluate Reverse Polish Notation 21.10% Medium
149 Max Points on a Line 12.60% Hard
148 Sort List 22.00% Medium
147 Insertion Sort List 26.40% Medium
146 LRU Cache 15.00% Hard
145 Binary Tree Postorder Traversal 32.40% Hard
144 Binary Tree Preorder Traversal 36.30% Medium
143 Reorder List 21.00% Medium
142 Linked List Cycle II 31.40% Medium
141 Linked List Cycle 36.30% Medium
140 Word Break II 17.70% Hard
139 Word Break 23.00% Medium
138 Copy List with Random Pointer 25.10% Hard
137 Single Number II 35.00% Medium
136 Single Number 45.10% Medium
135 Candy 20.50% Hard
134 Gas Station 25.70% Medium
133 Clone Graph 24.00% Medium
132 Palindrome Partitioning II 19.70% Hard
131 Palindrome Partitioning 26.70% Medium
130 Surrounded Regions 14.60% Medium
129 Sum Root to Leaf Numbers 30.30% Medium
128 Longest Consecutive Sequence 29.40% Hard
127 Word Ladder 19.30% Medium
126 Word Ladder II 12.90% Hard
125 Valid Palindrome 22.00% Easy
124 Binary Tree Maximum Path Sum 21.50% Hard
123 Best Time to Buy and Sell Stock III 23.90% Hard
122 Best Time to Buy and Sell Stock II 38.30% Medium
121 Best Time to Buy and Sell Stock 32.60% Medium
120 Triangle 27.40% Medium
119 Pascal's Triangle II 29.40% Easy
118 Pascal's Triangle 30.10% Easy
117 Populating Next Right Pointers in Each Node II 32.00% Hard
116 Populating Next Right Pointers in Each Node 36.20% Medium
115 Distinct Subsequences 26.30% Hard
114 Flatten Binary Tree to Linked List 28.80% Medium
113 Path Sum II 26.60% Medium
112 Path Sum 29.80% Easy
111 Minimum Depth of Binary Tree 29.10% Easy
110 Balanced Binary Tree 32.00% Easy
109 Convert Sorted List to Binary Search Tree 27.90% Medium
108 Convert Sorted Array to Binary Search Tree 34.00% Medium
107 Binary Tree Level Order Traversal II 31.10% Easy
106 Construct Binary Tree from Inorder and Postorder Traversal 26.80% Medium
105 Construct Binary Tree from Preorder and Inorder Traversal 26.40% Medium
104 Maximum Depth of Binary Tree 45.10% Easy
103 Binary Tree Zigzag Level Order Traversal 26.40% Medium
102 Binary Tree Level Order Traversal 29.30% Easy
101 Symmetric Tree 31.60% Easy
100 Same Tree 41.80% Easy
99 Recover Binary Search Tree 24.30% Hard
98 Validate Binary Search Tree 20.60% Medium
97 Interleaving String 20.80% Hard
96 Unique Binary Search Trees 36.00% Medium
95 Unique Binary Search Trees II 28.00% Medium
94 Binary Tree Inorder Traversal 36.20% Medium
93 Restore IP Addresses 21.00% Medium
92 Reverse Linked List II 26.10% Medium
91 Decode Ways 16.40% Medium
90 Subsets II 27.70% Medium
89 Gray Code 32.80% Medium
88 Merge Sorted Array 29.70% Easy
87 Scramble String 24.20% Hard
86 Partition List 27.50% Medium
85 Maximal Rectangle 22.00% Hard
84 Largest Rectangle in Histogram 22.60% Hard
83 Remove Duplicates from Sorted List 34.50% Easy
82 Remove Duplicates from Sorted List II 25.00% Medium
81 Search in Rotated Sorted Array II 31.40% Medium
80 Remove Duplicates from Sorted Array II 30.50% Medium
79 Word Search 20.30% Medium
78 Subsets 28.20% Medium
77 Combinations 30.90% Medium
76 Minimum Window Substring 18.90% Hard
75 Sort Colors 32.60% Medium
74 Search a 2D Matrix 31.60% Medium
73 Set Matrix Zeroes 31.40% Medium
72 Edit Distance 26.20% Hard
71 Simplify Path 20.00% Medium
70 Climbing Stairs 34.40% Easy
69 Sqrt(x) 23.10% Medium
68 Text Justification 14.60% Hard
67 Add Binary 24.70% Easy
66 Plus One 30.40% Easy
65 Valid Number 11.40% Hard
64 Minimum Path Sum 32.20% Medium
63 Unique Paths II 28.00% Medium
62 Unique Paths 32.80% Medium
61 Rotate List 21.70% Medium
60 Permutation Sequence 22.80% Medium
59 Spiral Matrix II 31.80% Medium
58 Length of Last Word 28.00% Easy
57 Insert Interval 21.40% Hard
56 Merge Intervals 22.40% Hard
55 Jump Game 27.00% Medium
54 Spiral Matrix 20.80% Medium
53 Maximum Subarray 34.50% Medium
52 N-Queens II 35.80% Hard
51 N-Queens 26.50% Hard
50 Pow(x, n) 26.70%
49 Anagrams 24.30% Medium
48 Rotate Image 31.90% Medium
47 Permutations II 25.80% Hard
46 Permutations 31.90% Medium
45 Jump Game II 24.20% Hard
44 Wildcard Matching 15.00% Hard
43 Multiply Strings 21.00% Medium
42 Trapping Rain Water 30.00% Hard
41 First Missing Positive 22.90% Hard
40 Combination Sum II 25.20% Medium
39 Combination Sum 27.90% Medium
38 Count and Say 25.20% Easy
37 Sudoku Solver 21.80% Hard
36 Valid Sudoku 27.20% Easy
35 Search Insert Position 35.40% Medium
34 Search for a Range 27.50% Medium
33 Search in Rotated Sorted Array 28.80% Hard
32 Longest Valid Parentheses 20.90% Hard
31 Next Permutation 25.00% Medium
30 Substring with Concatenation of All Words 19.40% Hard
29 Divide Two Integers 15.00% Medium
28 Implement strStr() 22.20% Easy
27 Remove Element 32.10% Easy
26 Remove Duplicates from Sorted Array 31.30% Easy
25 Reverse Nodes in k-Group 25.50% Hard
24 Swap Nodes in Pairs 32.50% Medium
23 Merge k Sorted Lists 21.10% Hard
22 Generate Parentheses 32.60% Medium
21 Merge Two Sorted Lists 32.70% Easy
20 Valid Parentheses 26.50% Easy
19 Remove Nth Node From End of List 27.10% Easy
18 4Sum 21.70% Medium
17 Letter Combinations of a Phone Number 25.80% Medium
16 3Sum Closest 26.90% Medium
15 3Sum 16.90% Medium
14 Longest Common Prefix 25.50% Easy
13 Roman to Integer 34.00% Easy
12 Integer to Roman 33.80% Medium
11 Container With Most Water 32.00% Medium
10 Regular Expression Matching 20.70% Hard
9 Palindrome Number 28.30% Easy
8 String to Integer (atoi) 13.00% Easy
7 Reverse Integer 25.10% Easy
6 ZigZag Conversion 21.80% Easy
5 Longest Palindromic Substring 20.70% Medium
4 Median of Two Sorted Arrays 17.40% Hard
3 Longest Substring Without Repeating Characters 20.60% Medium
2 Add Two Numbers 21.10% Medium
1 Two Sum 17.70% Medium

WeChat Official Subscription Account

Welcome to follow my subscription account on Wechat, I will update the newest updated problem solution and explanation. And also welcome to add me as your wechat friend.



iOS APP - Leetcode Meet Me

This app displays all practical coding problems from leetcode.com, and provids the solutions.


Available on Apple Store:

Not available anymore.


Wechat Reward

If you like this project and want to sponsor the author, you can reward the author using Wechat by scanning the following QR code.


Or you can also sponsor me by scanning the following Venmo QR code.



Special Thanks

Special thanks to @notfresh to help extract the blog content in markdown format from my cnblogs.

leetcode's People

Contributors

grandyang avatar wangshijun avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

leetcode's Issues

[LeetCode] 3. Longest Substring Without Repeating Characters


请点击下方图片观看讲解视频
Click below image to watch YouTube Video
Video

Given a string s, find the length of the longest substring without repeating characters.

Example 1:

**Input:** s = "abcabcbb"
**Output:** 3
**Explanation:** The answer is "abc", with the length of 3.

Example 2:

**Input:** s = "bbbbb"
**Output:** 1
**Explanation:** The answer is "b", with the length of 1.

Example 3:

**Input:** s = "pwwkew"
**Output:** 3
**Explanation:** The answer is "wke", with the length of 3.
Notice that the answer must be a substring, "pwke" is a subsequence and not a substring.

Constraints:

  • 0 <= s.length <= 5 * 104
  • s consists of English letters, digits, symbols and spaces.

这道求最长无重复子串的题和之前那道 Isomorphic Strings 很类似,属于 LeetCode 早期经典题目,博主认为是可以跟 Two Sum 媲美的一道题。给了我们一个字符串,让求最长的无重复字符的子串,注意这里是子串,不是子序列,所以必须是连续的。先不考虑代码怎么实现,如果给一个例子中的例子 "abcabcbb",让你手动找无重复字符的子串,该怎么找。博主会一个字符一个字符的遍历,比如 a,b,c,然后又出现了一个a,那么此时就应该去掉第一次出现的a,然后继续往后,又出现了一个b,则应该去掉一次出现的b,以此类推,最终发现最长的长度为3。所以说,需要记录之前出现过的字符,记录的方式有很多,最常见的是统计字符出现的个数,但是这道题字符出现的位置很重要,所以可以使用 HashMap 来建立字符和其出现位置之间的映射。进一步考虑,由于字符会重复出现,到底是保存所有出现的位置呢,还是只记录一个位置?我们之前手动推导的方法实际上是维护了一个滑动窗口,窗口内的都是没有重复的字符,需要尽可能的扩大窗口的大小。由于窗口在不停向右滑动,所以只关心每个字符最后出现的位置,并建立映射。窗口的右边界就是当前遍历到的字符的位置,为了求出窗口的大小,需要一个变量 left 来指向滑动窗口的左边界,这样,如果当前遍历到的字符从未出现过,那么直接扩大右边界,如果之前出现过,那么就分两种情况,在或不在滑动窗口内,如果不在滑动窗口内,那么就没事,当前字符可以加进来,如果在的话,就需要先在滑动窗口内去掉这个已经出现过的字符了,去掉的方法并不需要将左边界 left 一位一位向右遍历查找,由于 HashMap 已经保存了该重复字符最后出现的位置,所以直接移动 left 指针就可以了。维护一个结果 res,每次用出现过的窗口大小来更新结果 res,就可以得到最终结果啦。

这里可以建立一个 HashMap,建立每个字符和其最后出现位置之间的映射,然后需要定义两个变量 res 和 left,其中 res 用来记录最长无重复子串的长度,left 指向该无重复子串左边的起始位置的前一个,由于是前一个,所以初始化就是 -1,然后遍历整个字符串,对于每一个遍历到的字符,如果该字符已经在 HashMap 中存在了,并且如果其映射值大于 left 的话,那么更新 left 为当前映射值。然后映射值更新为当前坐标i,这样保证了 left 始终为当前边界的前一个位置,然后计算窗口长度的时候,直接用 i-left 即可,用来更新结果 res。

这里解释下程序中那个 if 条件语句中的两个条件 m.count(s[i]) && m[s[i]] > left,因为一旦当前字符 s[i] 在 HashMap 已经存在映射,说明当前的字符已经出现过了,而若 m[s[i]] > left 成立,说明之前出现过的字符在窗口内,那么如果要加上当前这个重复的字符,就要移除之前的那个,所以让 left 赋值为 m[s[i]],由于 left 是窗口左边界的前一个位置(这也是 left 初始化为 -1 的原因,因为窗口左边界是从0开始遍历的),所以相当于已经移除出滑动窗口了。举一个最简单的例子 "aa",当 i=0 时,建立了 a->0 的映射,并且此时结果 res 更新为1,那么当 i=1 的时候,发现a在 HashMap 中,并且映射值0大于 left 的 -1,所以此时 left 更新为0,映射对更新为 a->1,那么此时 i-left 还为1,不用更新结果 res,那么最终结果 res 还为1,正确,代码如下:

C++ 解法一:

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int res = 0, left = -1, n = s.size();
        unordered_map<int, int> m;
        for (int i = 0; i < n; ++i) {
            if (m.count(s[i]) && m[s[i]] > left) {
                left = m[s[i]];  
            }
            m[s[i]] = i;
            res = max(res, i - left);            
        }
        return res;
    }
};

下面这种写法是上面解法的精简模式,这里我们可以建立一个 256 位大小的整型数组来代替 HashMap,这样做的原因是 ASCII 表共能表示 256 个字符,但是由于键盘只能表示 128 个字符,所以用 128 也行,然后全部初始化为 -1,这样的好处是不用像之前的 HashMap 一样要查找当前字符是否存在映射对了,对于每一个遍历到的字符,直接用其在数组中的值来更新 left,因为默认是 -1,而 left 初始化也是 -1,所以并不会产生错误,这样就省了 if 判断的步骤,其余思路都一样:

C++ 解法二:

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        vector<int> m(128, -1);
        int res = 0, left = -1;
        for (int i = 0; i < s.size(); ++i) {
            left = max(left, m[s[i]]);
            m[s[i]] = i;
            res = max(res, i - left);
        }
        return res;
    }
};

Java 解法二:

public class Solution {
    public int lengthOfLongestSubstring(String s) {
        int[] m = new int[256];
        Arrays.fill(m, -1);
        int res = 0, left = -1;
        for (int i = 0; i < s.length(); ++i) {
            left = Math.max(left, m[s.charAt(i)]);
            m[s.charAt(i)] = i;
            res = Math.max(res, i - left);
        }
        return res;
    }
}

下面这种解法使用了 HashSet,核心算法和上面的很类似,把出现过的字符都放入 HashSet 中,遇到 HashSet 中没有的字符就加入 HashSet 中并更新结果 res,如果遇到重复的,则从左边开始删字符,直到删到重复的字符停止:

C++ 解法三:

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int res = 0, left = 0, i = 0, n = s.size();
        unordered_set<char> t;
        while (i < n) {
            if (!t.count(s[i])) {
                t.insert(s[i++]);
                res = max(res, (int)t.size());
            } else {
                t.erase(s[left++]);
            }
        }
        return res;
    }
};

Java 解法三:

public class Solution {
    public int lengthOfLongestSubstring(String s) {
        int res = 0, left = 0, right = 0;
        HashSet<Character> t = new HashSet<Character>();
        while (right < s.length()) {
            if (!t.contains(s.charAt(right))) {
                t.add(s.charAt(right++));
                res = Math.max(res, t.size());
            } else {
                t.remove(s.charAt(left++));
            }
        }
        return res;
    }
}

Github 同步地址:

#3

类似题目:

Longest Substring with At Most Two Distinct Characters

Longest Substring with At Most K Distinct Characters

Subarrays with K Different Integers

参考资料:

https://leetcode.com/problems/longest-substring-without-repeating-characters/

https://leetcode.com/problems/longest-substring-without-repeating-characters/discuss/1737/C++-code-in-9-lines.

https://leetcode.com/problems/longest-substring-without-repeating-characters/discuss/1812/Share-my-Java-solution-using-HashSet

https://leetcode.com/problems/longest-substring-without-repeating-characters/discuss/1729/11-line-simple-Java-solution-O(n)-with-explanation

LeetCode All in One 题目讲解汇总(持续更新中...)

(欢迎加入博主的知识星球,博主将及时答疑解惑,并分享刷题经验与总结,快快加入吧~)

知识星球 喜欢请点赞,疼爱请打赏❤️~.~

微信打赏

|

Venmo 打赏


---|---

[LeetCode] 18. 4Sum


请点击下方图片观看讲解视频
Click below image to watch YouTube Video
Video

Given an array nums of n integers, return an array of all the unique quadruplets [nums[a], nums[b], nums[c], nums[d]] such that:

  • 0 <= a, b, c, d < n
  • a, b, c, and d are distinct.
  • nums[a] + nums[b] + nums[c] + nums[d] == target

You may return the answer in any order.

Example 1:

**Input:** nums = [1,0,-1,0,-2,2], target = 0
**Output:** [[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]

Example 2:

**Input:** nums = [2,2,2,2,2], target = 8
**Output:** [[2,2,2,2]] 

Constraints:

  • 1 <= nums.length <= 200
  • -10^9 <= nums[i] <= 10^9
  • -10^9 <= target <= 10^9

这道题给了一个整型数数组 nums 和一个目标值 target,让找出所有的四个数字的组合,使得其和等于给定的目标值 target。LeetCode 中关于数字之和还有其他几道,分别是 Two Sum3Sum3Sum Closest 等等,虽然难度在递增,但是整体的套路都是一样的,在这里为了避免重复项,我们使用了 STL 中的 TreeSet,其特点是不能有重复,如果新加入的数在 TreeSet 中原本就存在的话,插入操作就会失败,这样能很好的避免的重复项的存在。此题的 O(n^3) 解法的思路跟 3Sum 基本没啥区别,就是多加了一层 for 循环,其他的都一样,代码如下:

解法一:

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        set<vector<int>> res;
        sort(nums.begin(), nums.end());
        for (int i = 0; i < int(nums.size() - 3); ++i) {
            for (int j = i + 1; j < int(nums.size() - 2); ++j) {
                if (j > i + 1 && nums[j] == nums[j - 1]) continue;
                int left = j + 1, right = nums.size() - 1;
                while (left < right) {
                    long sum = (long)nums[i] + nums[j] + nums[left] + nums[right];
                    if (sum == target) {
                        vector<int> out{nums[i], nums[j], nums[left], nums[right]};
                        res.insert(out);
                        ++left; --right;
                    } else if (sum < target) ++left;
                    else --right;
                }
            }
        }
        return vector<vector<int>>(res.begin(), res.end());
    }
};

但是毕竟用 TreeSet 来进行去重复的处理还是有些取巧,可能在 Java 中就不能这么做,那么还是来看一种比较正统的做法吧,手动进行去重复处理。主要可以进行的有三个地方,首先在两个 for 循环下可以各放一个,因为一旦当前的数字跟上面处理过的数字相同了,那么找下来肯定还是重复的。之后就是当 sum 等于 target 的时候了,在将四个数字加入结果 res 之后,left 和 right 都需要去重复处理,分别像各自的方面遍历即可,参见代码如下:

解法二:

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        vector<vector<int>> res;
        int n = nums.size();
        sort(nums.begin(), nums.end());
        for (int i = 0; i < n - 3; ++i) {
            if (i > 0 && nums[i] == nums[i - 1]) continue;
            for (int j = i + 1; j < n - 2; ++j) {
                if (j > i + 1 && nums[j] == nums[j - 1]) continue;
                int left = j + 1, right = n - 1;
                while (left < right) {
                    long sum = (long)nums[i] + nums[j] + nums[left] + nums[right];
                    if (sum == target) {
                        vector<int> out{nums[i], nums[j], nums[left], nums[right]};
                        res.push_back(out);
                        while (left < right && nums[left] == nums[left + 1]) ++left;
                        while (left < right && nums[right] == nums[right - 1]) --right;
                        ++left; --right;
                    } else if (sum < target) ++left;
                    else --right;
                }
            }
        }
        return res;
    }
};

再来看一种更 generalize 的解法,为了避免 LeetCode 以后再出什么 5Sum 或 6Sum 的题目,我们还是来想一种 KSum 的解法吧,通用任意的数字。其实思路还是基于前面的经验,通过之前的 3Sum 和 4Sum 的题目,可以得出结论,都是要通过不断的固定数字,最后留出两个数字来进行双指针的遍历。对于 KSum 来说,就是要固定 K-2 的个数字,这里当然不能手动加 K-2 个 for 循环了,所以只能用递归来做,在 kSum 的函数中定义了一个递归函数 dfs,除了题目中给定的 nums 和 target 两个参数之外,还需要传入几个参数,分别是当前需要查找的个数k,查找范围左边界 left,右边界 right,以及当前数字组合 cur,和最终返回结果 res。

在递归函数中,判断若k等于2,说明此时可以使用双指针了,则进行 while 循环,若此时两个数字正好等于 target 了,则需要将两个数字都加入到 cur 中,然后将 cur 加入到结果 res 中。再将这两个数字从 cur 中移除,因为 cur 中本来可能有其他数字的,所以要移除新加的数字,以便尝试其他不同的组合。此时 left 和 right 分别跳过各自的重复数字,然后再分别移动一位即可。若两个数字之和小于 target,则左指针右移,否则右指针左移。若k大于2,则此时要固定数字,将 left 指向的数字加入到 cur 中,然后调用递归函数 dfs,此时的 target 要减去 nums[left](注意这里为了防止溢出,dfs 函数的 target 定义成了长整型),此时传入 k-1 和 left+1,之后恢复状态,将 nums[left] 从 cur 中移除。然后 left 指针跳过后面的重复数字,之后再向右移动一位即可,参见代码如下:

解法三:

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        vector<vector<int>> res;
        int n = nums.size();
        sort(nums.begin(), nums.end());
        kSum(nums, target, 4, res);
        return res;
    }
    void kSum(vector<int>& nums, int target, int k, vector<vector<int>>& res) {
        vector<int> cur;
        dfs(nums, target, k, 0, (int)nums.size() - 1, cur, res);
    }
    void dfs(vector<int>& nums, long target, int k, int left, int right, vector<int>& cur, vector<vector<int>>& res) {
        if (k == 2) {
            while (left < right) {
                if (nums[left] + nums[right] == target) {
                    cur.push_back(nums[left]);
                    cur.push_back(nums[right]);
                    res.push_back(cur);
                    cur.pop_back();
                    cur.pop_back();
                    while (left < right && nums[left] == nums[left + 1]) ++left;
                    while (left < right && nums[right] == nums[right - 1]) --right;
                    ++left; --right;
                } else if (nums[left] + nums[right] < target) {
                    ++left;
                } else {
                    --right;
                }
            }
        }
        while (left < right) {
            cur.push_back(nums[left]);
            dfs(nums, target - nums[left], k - 1, left + 1, right, cur, res);
            cur.pop_back();
            while (left < right && nums[left] == nums[left + 1]) ++left;
            ++left;
        }
    }
};

Github 同步地址:

#18

类似题目:

Two Sum

3Sum

4Sum II

Count Special Quadruplets

参考资料:

https://leetcode.com/problems/4sum/

https://leetcode.com/problems/4sum/discuss/8549/My-16ms-c%2B%2B-code

https://leetcode.com/problems/4sum/discuss/8575/Clean-accepted-java-O(n3)-solution-based-on-3sum

LeetCode All in One 题目讲解汇总(持续更新中...)

(欢迎加入博主的知识星球,博主将及时答疑解惑,并分享刷题经验与总结,快快加入吧~)

知识星球 喜欢请点赞,疼爱请打赏❤️~.~

微信打赏

|

Venmo 打赏


---|---

[LeetCode] 49. Group Anagrams


请点击下方图片观看讲解视频
Click below image to watch YouTube Video
Video

Given an array of strings strs, group the anagrams together. You can return the answer in any order.

An Anagram is a word or phrase formed by rearranging the letters of a different word or phrase, typically using all the original letters exactly once.

Example 1:

Input: strs = ["eat","tea","tan","ate","nat","bat"]
Output: [["bat"],["nat","tan"],["ate","eat","tea"]]

Example 2:

Input: strs = [""]
Output: [[""]]

Example 3:

Input: strs = ["a"]
Output: [["a"]]

Constraints:

  • 1 <= strs.length <= 10^4
  • 0 <= strs[i].length <= 100
  • strs[i] consists of lowercase English letters.

这道题让我们群组给定字符串集中所有的异位词,所谓的异位词就是两个字符串中字母出现的次数都一样,只是位置不同,比如 abc,bac, cba 等它们就互为异位词,那么如何判断两者是否是异位词呢,可以发现如果把异位词的字符顺序重新排列,那么会得到相同的结果,所以重新排序是判断是否互为异位词的方法,由于异位词重新排序后都会得到相同的字符串,以此作为 key,将所有异位词都保存到字符串数组中,建立 key 和当前的不同的异位词集合个数之间的映射,这里之所以没有建立 key 和其隶属的异位词集合之间的映射,是用了一个小 trick,从而避免了最后再将 HashMap 中的集合拷贝到结果 res 中。当检测到当前的单词不在 HashMap 中,此时知道这个单词将属于一个新的异位词集合,所以将其映射为当前的异位词集合的个数,然后在 res 中新增一个空集合,这样就可以通过其映射值,直接找到新的异位词集合的位置,从而将新的单词存入结果 res 中,参见代码如下:

解法一:

class Solution {
public:
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        vector<vector<string>> res;
        unordered_map<string, int> m;
        for (string str : strs) {
            string t = str;
            sort(t.begin(), t.end());
            if (!m.count(t)) {
                m[t] = res.size();
                res.push_back({});
            }
            res[m[t]].push_back(str);
        }
        return res;
    }
};

下面这种解法没有用到排序,用一个大小为 26 的 int 数组来统计每个单词中字符出现的次数,然后将 int 数组转为一个唯一的字符串,跟字符串数组进行映射,这样就不用给字符串排序了,参见代码如下:

解法二:

class Solution {
public:
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        vector<vector<string>> res;
        unordered_map<string, vector<string>> m;
        for (string str : strs) {
            vector<int> cnt(26);
            string t;
            for (char c : str) ++cnt[c - 'a'];
            for (int i = 0; i < 26; ++i) {
                if (cnt[i] == 0) continue;
                t += string(1, i + 'a') + to_string(cnt[i]);
            }
            m[t].push_back(str);
        }
        for (auto a : m) {
            res.push_back(a.second);
        }
        return res;
    }
};

Github 同步地址:

#49

类似题目:

Valid Anagram

Group Shifted Strings

Find Resultant Array After Removing Anagrams

Count Anagrams

参考资料:

https://leetcode.com/problems/group-anagrams/

https://leetcode.com/problems/group-anagrams/discuss/19176/share-my-short-java-solution

https://leetcode.com/problems/group-anagrams/discuss/19200/10-lines-76ms-easy-c-solution-updated-function-signature

LeetCode All in One 题目讲解汇总(持续更新中...)

(欢迎加入博主的知识星球,博主将及时答疑解惑,并分享刷题经验与总结,快快加入吧~)

知识星球 喜欢请点赞,疼爱请打赏❤️~.~

微信打赏

|

Venmo 打赏


---|---

[LeetCode] 33. Search in Rotated Sorted Array


请点击下方图片观看讲解视频
Click below image to watch YouTube Video
Video

There is an integer array nums sorted in ascending order (with distinct values).

Prior to being passed to your function, nums is possibly rotated at an unknown pivot index k (1 <= k < nums.length) such that the resulting array is [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]] ( 0-indexed ). For example, [0,1,2,4,5,6,7] might be rotated at pivot index 3 and become [4,5,6,7,0,1,2].

Given the array nums after the possible rotation and an integer target, return the index oftarget if it is innums , or-1 if it is not innums.

You must write an algorithm with O(log n) runtime complexity.

Example 1:

**Input:** nums = [4,5,6,7,0,1,2], target = 0
**Output:** 4

Example 2:

**Input:** nums = [4,5,6,7,0,1,2], target = 3
**Output:** -1

Example 3:

**Input:** nums = [1], target = 0
**Output:** -1

Constraints:

  • 1 <= nums.length <= 5000
  • -10^4 <= nums[i] <= 10^4
  • All values of nums are unique.
  • nums is an ascending array that is possibly rotated.
  • -10^4 <= target <= 10^4

这道题让在旋转数组中搜索一个给定值,若存在返回坐标,若不存在返回 -1。我们还是考虑二分搜索法,但是这道题的难点在于不知道原数组在哪旋转了,还是用题目中给的例子来分析,对于数组 [0 1 2 4 5 6 7] 共有下列七种旋转方法(红色表示中点之前或者之后一定为有序的):

0 1 2 4 5 6 7

7 0 1 2 4 5 6

6 7 0 1 2 4 5

5 6 7 0 1 2 4

4 5 6 7 0 1 2

2 4 5 6 7 0 1

1 2 4 5 6 7 0

二分搜索法的关键在于获得了中间数后,判断下面要搜索左半段还是右半段,观察上面红色的数字都是升序的(Github 中可能无法显示颜色,参见博客园上的帖子),可以得出出规律,如果中间的数小于最右边的数,则右半段是有序的,若中间数大于最右边数,则左半段是有序的,我们只要在有序的半段里用首尾两个数组来判断目标值是否在这一区域内,这样就可以确定保留哪半边了,代码如下:

解法一:

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0, right = nums.size() - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] == target) return mid;
            if (nums[mid] < nums[right]) {
                if (nums[mid] < target && nums[right] >= target) left = mid + 1;
                else right = mid - 1;
            } else {
                if (nums[left] <= target && nums[mid] > target) right = mid - 1;
                else left = mid + 1;
            }
        }
        return -1;
    }
};

看了上面的解法,你可能会产生个疑问,为啥非得用中间的数字跟最右边的比较呢?难道跟最左边的数字比较不行吗,当中间的数字大于最左边的数字时,左半段也是有序的啊,如下所示(蓝色表示中点之前一定为有序的):

0 1 2 4 5 6 7

7 0 1 2 4 5 6

6 7 0 1 2 4 5

5 6 7 0 1 2 4

4 5 6 7 0 1 2

2 4 5 6 7 0 1

1 2 4 5 6 7 0

貌似也可以做,但是有一个问题,那就是在二分搜索中,nums[mid] 和 nums[left] 还有可能是同一个数字,当数组中只有两个数字的时候,比如 [3, 1],那该去取那一边呢?由于只有两个数字且 nums[mid] 不等于 target,target 只有可能在右半边出现。最好的方法就是让其无法进入左半段,就需要左半段是有序的,而且由于一定无法同时满足 nums[left] <= target && nums[mid] > target,因为 nums[left] 和 nums[mid] 是同一个数字,同一个数怎么可能同时大于等于 target,又小于 target。由于这个条件不满足,则直接进入右半段继续搜索即可,所以等于的情况要加到 nums[mid] > nums[left] 的情况中,变成大于等于,参见代码如下:

解法二:

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0, right = nums.size() - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] == target) return mid;
            if (nums[mid] >= nums[left]) {
                if (nums[left] <= target && nums[mid] > target) right = mid - 1;
                else left = mid + 1;
            } else {
                if (nums[mid] < target && nums[right] >= target) left = mid + 1;
                else right = mid - 1;
            }
        }
        return -1;
    }
};

讨论:这道题的二分搜索的解法实际上是博主之前的总结帖 LeetCode Binary Search Summary 二分搜索法小结 中的第五类,也是必须要将 right 初始化为 nums.size()-1,且循环条件必须为小于等于。二分搜索法真是博大精深,变化莫测啊~

Github 同步地址:

#33

类似题目:

Search in Rotated Sorted Array II

Find Minimum in Rotated Sorted Array

Pour Water Between Buckets to Make Water Levels Equal

参考资料:

https://leetcode.com/problems/search-in-rotated-sorted-array/

https://leetcode.com/problems/search-in-rotated-sorted-array/discuss/14436/Revised-Binary-Search

https://leetcode.com/problems/search-in-rotated-sorted-array/discuss/14435/Clever-idea-making-it-simple

LeetCode All in One 题目讲解汇总(持续更新中...)

(欢迎加入博主的知识星球,博主将及时答疑解惑,并分享刷题经验与总结,快快加入吧~)

知识星球 喜欢请点赞,疼爱请打赏❤️~.~

微信打赏

|

Venmo 打赏


---|---

[LeetCode] 44. Wildcard Matching


请点击下方图片观看讲解视频
Click below image to watch YouTube Video
Video

Given an input string (s) and a pattern (p), implement wildcard pattern matching with support for '?' and '*' where:

  • '?' Matches any single character.
  • '*' Matches any sequence of characters (including the empty sequence).

The matching should cover the entire input string (not partial).

Example 1:

Input:
s = "aa"
p = "a"
Output: false
Explanation: "a" does not match the entire string "aa".

Example 2:

Input:
s = "aa"
p = "*"
Output: true
Explanation: '*' matches any sequence.

Example 3:

Input:
s = "cb"
p = "?a"
Output: false
Explanation: '?' matches 'c', but the second letter is 'a', which does not match 'b'.

Example 4:

Input:
s = "adceb"
p = "*a*b"
Output: true
Explanation: The first '*' matches the empty sequence, while the second '*' matches the substring "dce".

Example 5:

Input:
s = "acdcb"
p = "a*c?b"
Output: false 

Constraints:

  • 0 <= s.length, p.length <= 2000
  • s contains only lowercase English letters.
  • p contains only lowercase English letters, '?' or '*'.

这道题通配符外卡匹配问题还是小有难度的,有特殊字符 ‘’ 和 ‘?’,其中 ‘?’ 能代替任何字符,‘’ 能代替任何字符串,注意跟另一道 Regular Expression Matching 正则匹配的题目区分开来。两道题的星号的作用是不同的,注意对比区分一下。这道题最大的难点,就是对于星号的处理,可以匹配任意字符串,简直像开了挂一样,就是说在星号对应位置之前,不管你s中有任何字符串,我大星号都能匹配你,主角光环啊。但即便叼如斯的星号,也有其处理不了的问题,那就是一旦p中有s中不存在的字符,那么一定无法匹配,因为星号只能增加字符,不能消除字符,再有就是星号一旦确定了要匹配的字符串,对于星号位置后面的匹配情况也就鞭长莫及了。所以p串中星号的位置很重要,用 jStar 来表示,还有星号匹配到s串中的位置,使用 iStart 来表示,这里 iStar 和 jStar 均初始化为 -1,表示默认情况下是没有星号的。然后再用两个变量i和j分别指向当前s串和p串中遍历到的位置。

开始进行匹配,若i小于s串的长度,进行 while 循环。若当前两个字符相等,或着p中的字符是问号,则i和j分别加1。若 p[j] 是星号,要记录星号的位置,jStar 赋为j,此时j再自增1,iStar 赋为i。若当前 p[j] 不是星号,并且不能跟 p[i] 匹配上,此时就要靠星号了,若之前星号没出现过,那么就直接跪,比如 s = "aa" 和 p = "c*",此时 s[0] 和 p[0] 无法匹配,虽然 p[1] 是星号,但还是跪。如果星号之前出现过,可以强行续一波命,比如 s = "aa" 和 p = "*c",当发现 s[1] 和 p[1] 无法匹配时,但是好在之前 p[0] 出现了星号,把 s[1] 交给 p[0] 的星号去匹配。至于如何知道之前有没有星号,这时就能看出 iStar 的作用了,因为其初始化为 -1,而遇到星号时,其就会被更新为i,只要检测 iStar 的值,就能知道是否可以使用星号续命。虽然成功续了命,匹配完了s中的所有字符,但是之后还要检查p串,此时没匹配完的p串里只能剩星号,不能有其他的字符,将连续的星号过滤掉,如果j不等于p的长度,则返回 false,参见代码如下:

解法一:

class Solution {
public:
    bool isMatch(string s, string p) {
        int i = 0, j = 0, iStar = -1, jStar = -1, m = s.size(), n = p.size();
        while (i < m) {
            if (j < n && (s[i] == p[j] || p[j] == '?')) {
                ++i; ++j;
            } else if (j < n && p[j] == '*') {
                iStar = i;
                jStar = j++;
            } else if (iStar >= 0) {
                i = ++iStar;
                j = jStar + 1;
            } else return false;
        }
        while (j < n && p[j] == '*') ++j;
        return j == n;
    }
};

这道题也能用动态规划 Dynamic Programming 来解,写法跟之前那道题 Regular Expression Matching 很像,但是还是不一样。外卡匹配和正则匹配最大的区别就是在星号的使用规则上,对于正则匹配来说,星号不能单独存在,前面必须要有一个字符,而星号存在的意义就是表明前面这个字符的个数可以是任意个,包括0个,那么就是说即使前面这个字符并没有在s中出现过也无所谓,只要后面的能匹配上就可以了。而外卡匹配就不是这样的,外卡匹配中的星号跟前面的字符没有半毛钱关系,如果前面的字符没有匹配上,那么直接返回 false 了,根本不用管星号。而星号存在的作用是可以表示任意的字符串,当然只是当匹配字符串缺少一些字符的时候起作用,当匹配字符串p包含目标字符串s中没有的字符时,将无法成功匹配。

对于这种玩字符串的题目,动态规划 Dynamic Programming 是一大神器,因为字符串跟其子串之间的关系十分密切,正好适合 DP 这种靠推导状态转移方程的特性。那么先来定义 dp 数组吧,使用一个二维 dp 数组,其中 dp[i][j] 表示 s中前i个字符组成的子串和p中前j个字符组成的子串是否能匹配。大小初始化为 (m+1) x (n+1),加1的原因是要包含 dp[0][0] 的情况,因为若s和p都为空的话,也应该返回 true,所以也要初始化 dp[0][0] 为 true。还需要提前处理的一种情况是,当s为空,p为连续的星号时的情况。由于星号是可以代表空串的,所以只要s为空,那么连续的星号的位置都应该为 true,所以先将连续星号的位置都赋为 true。

然后就是推导一般的状态转移方程了,如何更新 dp[i][j],首先处理比较 tricky 的情况,若p中第j个字符是星号,由于星号可以匹配空串,所以如果p中的前 j-1 个字符跟s中前i个字符匹配成功了,即 dp[i][j-1] 为 true 的话,则 dp[i][j] 也能为 true。或者若p中的前j个字符跟s中的前 i-1 个字符匹配成功了,即 dp[i-1][j] 为true 的话,则 dp[i][j] 也能为 true,因为星号可以匹配任意字符串,再多加一个任意字符也没问题。若p中的第j个字符不是星号,对于一般情况,假设已经知道了s中前 i-1 个字符和p中前 j-1 个字符的匹配情况,即 dp[i-1][j-1],现在只需要匹配s中的第i个字符跟p中的第j个字符,若二者相等,即 s[i-1] == p[j-1],或者p中的第j个字符是问号,即 p[j-1] == '?',再与上 dp[i-1][j-1] 的值,就可以更新 dp[i][j] 了,参见代码如下:

解法二:

class Solution {
public:
    bool isMatch(string s, string p) {
        int m = s.size(), n = p.size();
        vector<vector<bool>> dp(m + 1, vector<bool>(n + 1));
        dp[0][0] = true;
        for (int i = 1; i <= n; ++i) {
            if (p[i - 1] == '*') dp[0][i] = dp[0][i - 1];
        }
        for (int i = 1; i <= m; ++i) {
            for (int j = 1; j <= n; ++j) {
                if (p[j - 1] == '*') {
                    dp[i][j] = dp[i - 1][j] || dp[i][j - 1];
                } else {
                    dp[i][j] = (s[i - 1] == p[j - 1] || p[j - 1] == '?') && dp[i - 1][j - 1];
                }
            }
        }
        return dp[m][n];
    }
};

其实这道题也可以使用递归来做,因为子串或者子数组这种形式,天然适合利用递归来做。但是愣了吧唧的递归跟暴力搜索并没有啥太大的区别,很容易被 OJ 毙掉,比如评论区六楼的那个 naive 的递归,其实完全是按照题目要求来的。首先判断s串,若为空,那么再看p串,若p为空,则为 true,或者跳过星号,继续调用递归。若s串不为空,且p串为空,则直接 false。若s串和p串均不为空,进行第一个字符的匹配,若相等,或者 p[0] 是问号,则跳过首字符,对后面的子串调用递归。若 p[0] 是星号,先尝试跳过s串的首字符,调用递归,若递归返回 true,则当前返回 true。否则尝试跳过p串的首字符,调用递归,若递归返回 true,则当前返回 true。但是很不幸,内存超出限制了 MLE,那么博主做了个简单的优化,跳过了连续的星号,参见评论区七楼的代码,但是这次时间超出了限制 TLE。

博主想是不是取子串 substr() 操作太费时间,且调用递归的适合s串和p串又分别建立了副本,才导致的 TLE。于是想着用坐标变量来代替取子串,并且递归函数调用的s串和p串都加上引用,代码参见评论区八楼,但尼玛还是跪了,OJ 大佬,刀下留人啊。最后还是在论坛上找到了一个使用了神奇的剪枝的方法,这种解法的递归函数返回类型不是 bool 型,而是整型,有三种不同的状态,返回0表示匹配到了s串的末尾,但是未匹配成功;返回1表示未匹配到s串的末尾就失败了;返回2表示成功匹配。那么只有返回值大于1,才表示成功匹配。至于为何失败的情况要分类,就是为了进行剪枝。

在递归函数中,若s串和p串都匹配完成了,返回状态2。若s串匹配完成了,但p串但当前字符不是星号,返回状态0。若s串未匹配完,p串匹配完了,返回状态1。若s串和p串均未匹配完,且当前字符成功匹配的话,对下一个位置调用递归。否则若p串当前字符是星号,首先跳过连续的星号。然后分别让星号匹配空串,一个字符,两个字符,....,直到匹配完整个s串,对每种情况分别调用递归函数,接下来就是最大的亮点了,也是最有用的剪枝,当前返回值为状态0或者2的时候,返回,否则继续遍历。如果仅仅是状态2的时候才返回,就像评论区八楼的代码,会有大量的重复计算,因为当返回值为状态0的时候,已经没有继续循环下去的必要了,非常重要的一刀剪枝,参见代码如下:

解法三:

class Solution {
public:
    bool isMatch(string s, string p) {
        return helper(s, p, 0, 0) > 1;
    }
    int helper(string& s, string& p, int i, int j) {
        if (i == s.size() && j == p.size()) return 2;
        if (i == s.size() && p[j] != '*') return 0;
        if (j == p.size()) return 1;
        if (s[i] == p[j] || p[j] == '?') {
            return helper(s, p, i + 1, j + 1);
        }
        if (p[j] == '*') {
            if (j + 1 < p.size() && p[j + 1] == '*') {
                return helper(s, p, i, j + 1);
            }
            for (int k = 0; k <= (int)s.size() - i; ++k) {
                int res = helper(s, p, i + k, j + 1);
                if (res == 0 || res == 2) return res;
            }
        }
        return 1;
    }
};

Github 同步地址:

#44

类似题目:

Regular Expression Matching

参考资料:

https://leetcode.com/problems/wildcard-matching/

https://leetcode.com/problems/wildcard-matching/discuss/17839/C%2B%2B-recursive-solution-16-ms

https://leetcode.com/problems/wildcard-matching/discuss/17910/clear-c-dp-solution-similar-to-the-last-matching-problem

https://leetcode.com/problems/wildcard-matching/discuss/17811/My-three-C%2B%2B-solutions-(iterative-(16ms)-and-DP-(180ms)-and-modified-recursion-(88ms))

LeetCode All in One 题目讲解汇总(持续更新中...)

(欢迎加入博主的知识星球,博主将及时答疑解惑,并分享刷题经验与总结,快快加入吧~)

知识星球 喜欢请点赞,疼爱请打赏❤️~.~

微信打赏

|

Venmo 打赏


---|---

[LeetCode] 38. Count and Say


请点击下方图片观看讲解视频
Click below image to watch YouTube Video
Video

The count-and-say sequence is a sequence of digit strings defined by the recursive formula:

  • countAndSay(1) = "1"
  • countAndSay(n) is the way you would "say" the digit string from countAndSay(n-1), which is then converted into a different digit string.

To determine how you "say" a digit string, split it into the minimal number of substrings such that each substring contains exactly one unique digit. Then for each substring, say the number of digits, then say the digit. Finally, concatenate every said digit.

For example, the saying and conversion for digit string "3322251":

Given a positive integer n, return thenth term of the count-and-say sequence.

Example 1:

**Input:** n = 1
**Output:** "1"
**Explanation:** This is the base case.

Example 2:

**Input:** n = 4
**Output:** "1211"
**Explanation:**
countAndSay(1) = "1"
countAndSay(2) = say "1" = one 1 = "11"
countAndSay(3) = say "11" = two 1's = "21"
countAndSay(4) = say "21" = one 2 + one 1 = "12" + "11" = "1211"

Constraints:

  • 1 <= n <= 30

这道计数和读法问题还是第一次遇到,看似挺复杂,其实仔细一看,算法很简单,就是对于前一个数,找出相同元素的个数,把个数和该元素存到新的 string 里。代码如下:

class Solution {
public:
    string countAndSay(int n) {
        string res = "1";
        while (--n) {
            string cur;
            for (int i = 0; i < res.size(); ++i) {
                int cnt = 1;
                while (i + 1 < res.size() && res[i] == res[i + 1]) {
                    ++cnt;
                    ++i;
                }
                cur += to_string(cnt) + res[i];
            }
            res = cur;
        }
        return res;
    }
};

博主出于好奇打印出了前 12 个数字,发现一个很有意思的现象,不管打印到后面多少位,出现的数字只是由 1, 2 和3 组成,网上也有人发现了并分析了原因,参见这个帖子,前十二个数字如下:

1
1 1
2 1
1 2 1 1
1 1 1 2 2 1
3 1 2 2 1 1
1 3 1 1 2 2 2 1
1 1 1 3 2 1 3 2 1 1
3 1 1 3 1 2 1 1 1 3 1 2 2 1
1 3 2 1 1 3 1 1 1 2 3 1 1 3 1 1 2 2 1 1
1 1 1 3 1 2 2 1 1 3 3 1 1 2 1 3 2 1 1 3 2 1 2 2 2 1
3 1 1 3 1 1 2 2 2 1 2 3 2 1 1 2 1 1 1 3 1 2 2 1 1 3 1 2 1 1 3 2 1 1

Github 同步地址:

#38

类似题目:

Encode and Decode Strings

String Compression

参考资料:

https://leetcode.com/problems/count-and-say/

https://leetcode.com/problems/count-and-say/discuss/16000/Show-an-Answer-in-Java

https://leetcode.com/problems/count-and-say/discuss/16043/C%2B%2B-solution-easy-understand

LeetCode All in One 题目讲解汇总(持续更新中...)

(欢迎加入博主的知识星球,博主将及时答疑解惑,并分享刷题经验与总结,快快加入吧~)

知识星球 喜欢请点赞,疼爱请打赏❤️~.~

微信打赏

|

Venmo 打赏


---|---

[LeetCode] 36. Valid Sudoku


请点击下方图片观看讲解视频
Click below image to watch YouTube Video
Video

Determine if a 9 x 9 Sudoku board is valid. Only the filled cells need to be validated according to the following rules :

  1. Each row must contain the digits 1-9 without repetition.
  2. Each column must contain the digits 1-9 without repetition.
  3. Each of the nine 3 x 3 sub-boxes of the grid must contain the digits 1-9 without repetition.

Note:

  • A Sudoku board (partially filled) could be valid but is not necessarily solvable.
  • Only the filled cells need to be validated according to the mentioned rules.

Example 1:

**Input:** board = 
[["5","3",".",".","7",".",".",".","."]
,["6",".",".","1","9","5",".",".","."]
,[".","9","8",".",".",".",".","6","."]
,["8",".",".",".","6",".",".",".","3"]
,["4",".",".","8",".","3",".",".","1"]
,["7",".",".",".","2",".",".",".","6"]
,[".","6",".",".",".",".","2","8","."]
,[".",".",".","4","1","9",".",".","5"]
,[".",".",".",".","8",".",".","7","9"]]
**Output:** true

Example 2:

**Input:** board = 
[["8","3",".",".","7",".",".",".","."]
,["6",".",".","1","9","5",".",".","."]
,[".","9","8",".",".",".",".","6","."]
,["8",".",".",".","6",".",".",".","3"]
,["4",".",".","8",".","3",".",".","1"]
,["7",".",".",".","2",".",".",".","6"]
,[".","6",".",".",".",".","2","8","."]
,[".",".",".","4","1","9",".",".","5"]
,[".",".",".",".","8",".",".","7","9"]]
**Output:** false
**Explanation:** Same as Example 1, except with the **5** in the top left corner being modified to **8**. Since there are two 8's in the top left 3x3 sub-box, it is invalid. 

Constraints:

  • board.length == 9
  • board[i].length == 9
  • board[i][j] is a digit 1-9 or '.'.

这道题让验证一个方阵是否为数独矩阵,想必数独游戏我们都玩过,就是给一个 9x9 大小的矩阵,可以分为9个 3x3 大小的矩阵,要求是每个小矩阵内必须都是1到9的数字不能有重复,同时大矩阵的横纵列也不能有重复数字,是一种很经典的益智类游戏,经常在飞机上看见有人拿着纸笔在填数,感觉是消磨时光的利器。这道题给了一个残缺的二维数组,让我们判断当前的这个数独数组是否合法,即要满足上述的条件。判断标准是看各行各列是否有重复数字,以及每个小的 3x3 的小方阵里面是否有重复数字,如果都无重复,则当前矩阵是数独矩阵,但不代表待数独矩阵有解,只是单纯的判断当前未填完的矩阵是否是数独矩阵。那么根据数独矩阵的定义,在遍历每个数字的时候,就看看包含当前位置的行和列以及 3x3 小方阵中是否已经出现该数字,这里需要三个 boolean 型矩阵,大小跟原数组相同,分别记录各行,各列,各小方阵是否出现某个数字,其中行和列标志下标很好对应,就是小方阵的下标需要稍稍转换一下,具体代码如下:

解法一:

class Solution {
public:
    bool isValidSudoku(vector<vector<char>>& board) {
        vector<vector<bool>> rowFlag(9, vector<bool>(9));
        vector<vector<bool>> colFlag(9, vector<bool>(9));
        vector<vector<bool>> cellFlag(9, vector<bool>(9));
        for (int i = 0; i < 9; ++i) {
            for (int j = 0; j < 9; ++j) {
                if (board[i][j] == '.') continue;
                int c = board[i][j] - '1';
                if (rowFlag[i][c] || colFlag[c][j] || cellFlag[3 * (i / 3) + j / 3][c]) return false;
                rowFlag[i][c] = true;
                colFlag[c][j] = true;
                cellFlag[3 * (i / 3) + j / 3][c] = true;
            }
        }
        return true;
    }
};

我们也可以对空间进行些优化,只使用一个 HashSet 来记录已经存在过的状态,将每个状态编码成为一个字符串,能将如此大量信息的状态编码成一个单一的字符串还是需要有些技巧的。对于每个1到9内的数字来说,其在每行每列和每个小区间内都是唯一的,将数字放在一个括号中,每行上的数字就将行号放在括号左边,每列上的数字就将列数放在括号右边,每个小区间内的数字就将在小区间内的行列数分别放在括号的左右两边,这样每个数字的状态都是独一无二的存在,就可以在 HashSet 中愉快地查找是否有重复存在啦,参见代码如下:

解法二:

class Solution {
public:
    bool isValidSudoku(vector<vector<char>>& board) {
        unordered_set<string> st;
        for (int i = 0; i < 9; ++i) {
            for (int j = 0; j < 9; ++j) {
                if (board[i][j] == '.') continue;
                string t = "(" + to_string(board[i][j] - '0') + ")";
                string row = to_string(i) + t, col = t + to_string(j), cell = to_string(i / 3) + t + to_string(j / 3);
                if (st.count(row) || st.count(col) || st.count(cell)) return false;
                st.insert(row);
                st.insert(col);
                st.insert(cell);
            }
        }
        return true;
    }
};

Github 同步地址:

#36

类似题目:

Sudoku Solver

Check if Every Row and Column Contains All Numbers

参考资料:

https://leetcode.com/problems/valid-sudoku/

https://leetcode.com/problems/valid-sudoku/discuss/15450/Shared-my-concise-Java-code

https://leetcode.com/problems/valid-sudoku/discuss/15472/Short%2BSimple-Java-using-Strings

LeetCode All in One 题目讲解汇总(持续更新中...)

(欢迎加入博主的知识星球,博主将及时答疑解惑,并分享刷题经验与总结,快快加入吧~)

知识星球 喜欢请点赞,疼爱请打赏❤️~.~

微信打赏

|

Venmo 打赏


---|---

[LeetCode] 30. Substring with Concatenation of All Words


请点击下方图片观看讲解视频
Click below image to watch YouTube Video
Video

You are given a string s and an array of strings words. All the strings of words are of the same length.

A concatenated substring in s is a substring that contains all the strings of any permutation of words concatenated.

  • For example, if words = ["ab","cd","ef"], then "abcdef", "abefcd", "cdabef", "cdefab", "efabcd", and "efcdab" are all concatenated strings. "acdbef" is not a concatenated substring because it is not the concatenation of any permutation of words.

Return the starting indices of all the concatenated substrings ins. You can return the answer in any order.

Example 1:

**Input:** s = "barfoothefoobarman", words = ["foo","bar"]
**Output:** [0,9]
**Explanation:** Since words.length == 2 and words[i].length == 3, the concatenated substring has to be of length 6.
The substring starting at 0 is "barfoo". It is the concatenation of ["bar","foo"] which is a permutation of words.
The substring starting at 9 is "foobar". It is the concatenation of ["foo","bar"] which is a permutation of words.
The output order does not matter. Returning [9,0] is fine too.

Example 2:

**Input:** s = "wordgoodgoodgoodbestword", words = ["word","good","best","word"]
**Output:** []
**Explanation:** Since words.length == 4 and words[i].length == 4, the concatenated substring has to be of length 16.
There is no substring of length 16 in s that is equal to the concatenation of any permutation of words.
We return an empty array.

Example 3:

**Input:** s = "barfoofoobarthefoobarman", words = ["bar","foo","the"]
**Output:** [6,9,12]
**Explanation:** Since words.length == 3 and words[i].length == 3, the concatenated substring has to be of length 9.
The substring starting at 6 is "foobarthe". It is the concatenation of ["foo","bar","the"] which is a permutation of words.
The substring starting at 9 is "barthefoo". It is the concatenation of ["bar","the","foo"] which is a permutation of words.
The substring starting at 12 is "thefoobar". It is the concatenation of ["the","foo","bar"] which is a permutation of words.

Constraints:

  • 1 <= s.length <= 10^4
  • 1 <= words.length <= 5000
  • 1 <= words[i].length <= 30
  • s and words[i] consist of lowercase English letters.

这道题让我们求串联所有单词的子串,就是说给定一个长字符串,再给定几个长度相同的单词,让找出串联给定所有单词的子串的起始位置,还是蛮有难度的一道题。假设 words 数组中有 cnt 个单词,每个单词的长度均为 len,那么实际上这道题就让我们出所有长度为 cnt x len 的子串,使得其刚好是由 words 数组中的所有单词组成。那么就需要经常判断s串中长度为 len 的子串是否是 words 中的单词,为了快速的判断,可以使用 HashMap,同时由于 words 数组可能有重复单词,就要用 HashMap 来建立所有的单词和其出现次数之间的映射,即统计每个单词出现的次数。

遍历s中所有长度为 cnt x len 的子串,当剩余子串的长度小于 cnt x len 时,就不用再判断了。所以i从0开始,到 n - cnt x len 结束就可以了,n为原字符串s的长度。对于每个遍历到的长度为 cnt x len 的子串,需要验证其是否刚好由 words 中所有的单词构成,检查方法就是每次取长度为 len 的子串,看其是否是 words 中的单词。为了方便比较,建立另一个 HashMap,当取出的单词不在 words 中,直接 break 掉,否则就将其在新的 HashMap 中的映射值加1,还要检测若其映射值超过原 HashMap 中的映射值,也 break 掉,因为就算当前单词在 words 中,但若其出现的次数超过 words 中的次数,还是不合题意的。在 for 循环外面,若j正好等于 cnt,说明检测的 cnt 个长度为 len 的子串都是 words 中的单词,并且刚好构成了 words,则将当前位置i加入结果 res 即可,具体参见代码如下( 现在这种解法已经超时了,无法通过 OJ ):

解法一:

// Time Limit Exceeded
class Solution {
public:
    vector<int> findSubstring(string s, vector<string>& words) {
        if (s.empty() || words.empty()) return {};
        vector<int> res;
        int n = s.size(), cnt = words.size(), len = words[0].size();
        unordered_map<string, int> wordCnt;
        for (auto &word : words) ++wordCnt[word];
        for (int i = 0; i <= n - cnt * len; ++i) {
            unordered_map<string, int> strCnt;
            int j = 0; 
            for (j = 0; j < cnt; ++j) {
                string t = s.substr(i + j * len, len);
                if (!wordCnt.count(t)) break;
                ++strCnt[t];
                if (strCnt[t] > wordCnt[t]) break;
            }
            if (j == cnt) res.push_back(i);
        }
        return res;
    }
};

这道题还有一种 O(n) 时间复杂度的解法,设计思路非常巧妙,但是感觉很难想出来,博主目测还未到达这种水平。这种方法不再是一个字符一个字符的遍历,而是一个词一个词的遍历,比如根据题目中的例子,字符串s的长度n为 18,words 数组中有两个单词 (cnt=2),每个单词的长度 len 均为3,那么遍历的顺序为 0,3,6,9,12,15,然后偏移一个字符 1,4,7,10,13,16,然后再偏移一个字符 2,5,8,11,14,17,这样就可以把所有情况都遍历到,还是先用一个 HashMap 来建立所有单词和其出现次数之间的映射,变量名为 wordMap。然后从0开始遍历,用 left 来记录左边界的位置,curCnt 表示当前已经匹配的单词的个数。然后一个单词一个单词的遍历,如果当前遍历的到的单词 word 在 wordMap 中存在,那么将其加入另一个 HashMap 中,变量名为 curMap。如果在 curMap 中word 的个数小于等于 wordMap 中的个数,那么 curCnt 自增1,如果大于了,则需要做一些处理,比如下面这种情况:s = barfoofoo, words = {bar, foo, abc},给 words 中新加了一个 abc ,目的是为了遍历到 barfoo 不会停止,当遍历到第二 foo 的时候, curMap[foo]=2, 而此时 wordMap[foo]=1,这时候已经不连续了,所以要移动左边界 left 的位置,先把第一个词 bar 取出来,然后将 curMap[bar] 自减1,如果此时 curMap[bar] < wordMap[bar] 了,说明一个匹配没了,那么对应的 curCnt 也要自减1,然后左边界加上个 len,这样就可以了。如果某个时刻 curCnt 和 cnt 相等了,说明成功匹配了一个位置,将当前左边界 left 存入结果 res 中,此时去掉最左边的一个词,同时 curCnt 自减1,左边界右移 len,继续匹配。如果匹配到一个不在 wordMap 中的词,说明跟前面已经断开了,重置 curMap,curCnt 为0,左边界 left 移到 j+len,参见代码如下:

解法二:

class Solution {
public:
    vector<int> findSubstring(string s, vector<string>& words) {
        if (s.empty() || words.empty()) return {};
        vector<int> res;
        int n = s.size(), cnt = words.size(), len = words[0].size();
        unordered_map<string, int> wordMap;
        for (string word : words) ++wordMap[word];
        for (int i = 0; i < len; ++i) {
            int left = i, curCnt = 0;
            unordered_map<string, int> curMap;
            for (int j = i; j <= n - len; j += len) {
                string word = s.substr(j, len);
                if (wordMap.count(word)) {
                    ++curMap[word];
                    if (curMap[word] <= wordMap[word]) {
                        ++curCnt;
                    } else {
                        while (curMap[word] > wordMap[word]) {
                            string t = s.substr(left, len);
                            --curMap[t];
                            if (curMap[t] < wordMap[t]) --curCnt;
                            left += len;
                        }
                    }
                    if (curCnt == cnt) {
                        res.push_back(left);
                        --curMap[s.substr(left, len)];
                        --curCnt;
                        left += len;
                    }
                } else {
                    curMap.clear();
                    curCnt = 0;
                    left = j + len;
                }
            }
        }
        return res;
    }
};

Github 同步地址:

#30

类似题目:

Minimum Window Substring

参考资料:

https://leetcode.com/problems/substring-with-concatenation-of-all-words/

https://leetcode.com/problems/substring-with-concatenation-of-all-words/discuss/13656/An-O(N)-solution-with-detailed-explanation

https://leetcode.com/problems/substring-with-concatenation-of-all-words/discuss/13658/Easy-Two-Map-Solution-(C%2B%2BJava)

https://leetcode.com/problems/substring-with-concatenation-of-all-words/discuss/13664/Simple-Java-Solution-with-Two-Pointers-and-Map

LeetCode All in One 题目讲解汇总(持续更新中...)

(欢迎加入博主的知识星球,博主将及时答疑解惑,并分享刷题经验与总结,快快加入吧~)

知识星球 喜欢请点赞,疼爱请打赏❤️~.~

微信打赏

|

Venmo 打赏


---|---

[LeetCode] 40. Combination Sum II


请点击下方图片观看讲解视频
Click below image to watch YouTube Video
Video

Given a collection of candidate numbers (candidates) and a target number (target), find all unique combinations in candidates where the candidate numbers sum to target.

Each number in candidates may only be used once in the combination.

Note: The solution set must not contain duplicate combinations.

Example 1:

Input: candidates = [10,1,2,7,6,1,5], target = 8
Output: 
[
[1,1,6],
[1,2,5],
[1,7],
[2,6]
]

Example 2:

Input: candidates = [2,5,2,1,2], target = 5
Output: 
[
[1,2,2],
[5]
]

Constraints:

  • 1 <= candidates.length <= 100
  • 1 <= candidates[i] <= 50
  • 1 <= target <= 30

这道题跟之前那道 Combination Sum 本质没有区别,只需要改动一点点即可,之前那道题给定数组中的数字可以重复使用,而这道题不能重复使用,只需要在之前的基础上修改几个地方即可,首先要给数组排个序,然后在递归的 for 循环里加上 if (i > start && num[i] == num[i - 1]) continue; 这样可以防止 res 中出现重复项,最后就在递归调用 dfs 里面的参数换成 i+1,这样就不会重复使用数组中的数字了,代码如下:

解法一:

class Solution {
public:
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        vector<vector<int>> res;
        vector<int> cur;
        sort(candidates.begin(), candidates.end());
        dfs(candidates, target, 0, cur, res);
        return res;
    }
    void dfs(vector<int>& candidates, int target, int start, vector<int>& cur, vector<vector<int>>& res) {
        if (target < 0) return;
        if (target == 0) { res.push_back(cur); return; }
        for (int i = start; i < candidates.size(); ++i) {
            if (i > start && candidates[i] == candidates[i - 1]) continue;
            cur.push_back(candidates[i]);
            dfs(candidates, target - candidates[i], i + 1, cur, res);
            cur.pop_back();
        }
    }
};

对于之前的解法二可以通过稍微改动而适用于这里,同样的在处理当前数字 candidates[i] 时,和之前的数字比较,要跳过重复数字。其次就是在为下次递归创建新数组时,不能包括当前的数字,这样的话才能保证不重复使用数字,参见代码如下:

解法二:

class Solution {
public:
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        vector<vector<int>> res;
        sort(candidates.begin(), candidates.end());
        for (int i = 0; i < candidates.size(); ++i) {
            if (candidates[i] > target) break;
            if (candidates[i] == target) { res.push_back({candidates[i]}); break; }
            if (i > 0 && candidates[i] == candidates[i - 1]) continue;
            vector<int> vec = vector<int>(candidates.begin() + i + 1, candidates.end());
            vector<vector<int>> tmp = combinationSum2(vec, target - candidates[i]);
            for (auto a : tmp) {
                a.insert(a.begin(), candidates[i]);
                res.push_back(a);
            }
        }
        return res;
    }
};

对于 DP 解法来说,改变就比较大了,甚至可以说是完全不同的解法也不为过,这种原数组中有重复数字,且每个数字只能使用一次的要求,不太适合用 DP 来做,但也可以强行使用 DP 来做,只不过稍微有点麻烦。

解法三:

class Solution {
public:
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        vector<vector<vector<int>>> dp(target + 1);
        dp[0].resize(1);
        map<int, int> numCnt;
        for (int num : candidates) {
            ++numCnt[num];
        }
        for (auto a : numCnt) {
            int num = a.first, cnt = a.second;
            for (int i = target - num; i >= 0; --i) {
                for (auto v : dp[i]) {
                    int sum = i;
                    for (int k = 0; k < cnt && sum <= target - num; ++k) {
                        sum += num;
                        v.push_back(num);
                        dp[sum].push_back(v);
                    }
                }
            }
        }
        return dp[target];
    }
};

Github 同步地址:

#40

类似题目:

Combination Sum III

Combination Sum

参考资料:

https://leetcode.com/problems/combination-sum-ii/

https://leetcode.com/problems/combination-sum-ii/discuss/16861/Java-solution-using-dfs-easy-understand

https://leetcode.com/problems/combination-sum-ii/discuss/16878/Combination-Sum-I-II-and-III-Java-solution-(see-the-similarities-yourself)

LeetCode All in One 题目讲解汇总(持续更新中...)

(欢迎加入博主的知识星球,博主将及时答疑解惑,并分享刷题经验与总结,快快加入吧~)

知识星球 喜欢请点赞,疼爱请打赏❤️~.~

微信打赏

|

Venmo 打赏


---|---

[LeetCode] 8. String to Integer (atoi)


请点击下方图片观看讲解视频
Click below image to watch YouTube Video
Video

Implement the myAtoi(string s) function, which converts a string to a 32-bit signed integer (similar to C/C++'s atoi function).

The algorithm for myAtoi(string s) is as follows:

  1. Read in and ignore any leading whitespace.
  2. Check if the next character (if not already at the end of the string) is '-' or '+'. Read this character in if it is either. This determines if the final result is negative or positive respectively. Assume the result is positive if neither is present.
  3. Read in next the characters until the next non-digit character or the end of the input is reached. The rest of the string is ignored.
  4. Convert these digits into an integer (i.e. "123" -> 123, "0032" -> 32). If no digits were read, then the integer is 0. Change the sign as necessary (from step 2).
  5. If the integer is out of the 32-bit signed integer range [-231, 231 - 1], then clamp the integer so that it remains in the range. Specifically, integers less than -231 should be clamped to -231, and integers greater than 231 - 1 should be clamped to 231 - 1.
  6. Return the integer as the final result.

Note:

  • Only the space character ' ' is considered a whitespace character.
  • Do not ignore any characters other than the leading whitespace or the rest of the string after the digits.

Example 1:

**Input:** s = "42"
**Output:** 42
**Explanation:** The underlined characters are what is read in, the caret is the current reader position.
Step 1: "42" (no characters read because there is no leading whitespace)
         ^
Step 2: "42" (no characters read because there is neither a '-' nor '+')
         ^
Step 3: "42" ("42" is read in)
           ^
The parsed integer is 42.
Since 42 is in the range [-231, 231 - 1], the final result is 42.

Example 2:

**Input:** s = "   -42"
**Output:** -42
**Explanation:**
Step 1: "-42" (leading whitespace is read and ignored)
            ^
Step 2: "   -42" ('-' is read, so the result should be negative)
             ^
Step 3: "   -42" ("42" is read in)
               ^
The parsed integer is -42.
Since -42 is in the range [-231, 231 - 1], the final result is -42.

Example 3:

**Input:** s = "4193 with words"
**Output:** 4193
**Explanation:**
Step 1: "4193 with words" (no characters read because there is no leading whitespace)
         ^
Step 2: "4193 with words" (no characters read because there is neither a '-' nor '+')
         ^
Step 3: "4193 with words" ("4193" is read in; reading stops because the next character is a non-digit)
             ^
The parsed integer is 4193.
Since 4193 is in the range [-231, 231 - 1], the final result is 4193.

Example 4:

**Input:** "words and 987"
**Output:** 0
**Explanation:** The first non-whitespace character is 'w', which is not a numerical 
             digit or a +/- sign. Therefore no valid conversion could be performed.

Example 5:

**Input:** "-91283472332"
**Output:** -2147483648
**Explanation:** The number "-91283472332" is out of the range of a 32-bit signed integer.
             Thefore INT_MIN (−231) is returned.

Constraints:

  • 0 <= s.length <= 200
  • s consists of English letters (lower-case and upper-case), digits (0-9), ' ', '+', '-', and '.'.

字符串转为整数是很常用的一个函数,由于输入的是字符串,所以需要考虑的情况有很多种。博主之前有一篇文章是关于验证一个字符串是否为数字的,参见 Valid Number。在那篇文章中,详细的讨论了各种情况,包括符号,自然数,小数点的出现位置,判断他们是否是数字。个人以为这道题也应该有这么多种情况。但是这题只需要考虑数字和符号的情况:

1. 若字符串开头是空格,则跳过所有空格,到第一个非空格字符,如果没有,则返回0.

2. 若第一个非空格字符是符号 +/-,则标记 sign 的真假,这道题还有个局限性,那就是在 c++ 里面,+-1 和-+1 都是认可的,都是 -1,而在此题里,则会返回0.

3. 若下一个字符不是数字,则返回0,完全不考虑小数点和自然数的情况,不过这样也好,起码省事了不少。

4. 如果下一个字符是数字,则转为整型存下来,若接下来再有非数字出现,则返回目前的结果。

5. 还需要考虑边界问题,如果超过了整型数的范围,则用边界值替代当前值。

C++ 解法:

class Solution {
public:
    int myAtoi(string str) {
        if (str.empty()) return 0;
        int sign = 1, base = 0, i = 0, n = str.size();
        while (i < n && str[i] == ' ') ++i;
        if (i < n && (str[i] == '+' || str[i] == '-')) {
            sign = (str[i++] == '+') ? 1 : -1;
        }
        while (i < n && str[i] >= '0' && str[i] <= '9') {
            if (base > INT_MAX / 10 || (base == INT_MAX / 10 && str[i] - '0' > 7)) {
                return (sign == 1) ? INT_MAX : INT_MIN;
            }
            base = 10 * base + (str[i++] - '0');
        }
        return base * sign;
    }
};

Java 解法:

public class Solution {
    public int myAtoi(String str) {
        if (str.isEmpty()) return 0;
        int sign = 1, base = 0, i = 0, n = str.length();
        while (i < n && str.charAt(i) == ' ') ++i;
        if (i < n && (str.charAt(i) == '+' || str.charAt(i) == '-')) {
            sign = (str.charAt(i++) == '+') ? 1 : -1;
        }
        while (i < n && str.charAt(i) >= '0' && str.charAt(i) <= '9') {
            if (base > Integer.MAX_VALUE / 10 || (base == Integer.MAX_VALUE / 10 && str.charAt(i) - '0' > 7)) {
                return (sign == 1) ? Integer.MAX_VALUE : Integer.MIN_VALUE;
            }
            base = 10 * base + (str.charAt(i++) - '0');
        }
        return base * sign;
    }
}

Github 同步地址:

#8

类似题目:

Reverse Integer

Valid Number

参考资料:

https://leetcode.com/problems/string-to-integer-atoi/

https://leetcode.com/problems/string-to-integer-atoi/discuss/4654/My-simple-solution

https://leetcode.com/problems/string-to-integer-atoi/discuss/4642/8ms-C%2B%2B-solution-easy-to-understand

LeetCode All in One 题目讲解汇总(持续更新中...)

(欢迎加入博主的知识星球,博主将及时答疑解惑,并分享刷题经验与总结,快快加入吧~)

知识星球 喜欢请点赞,疼爱请打赏❤️~.~

微信打赏

|

Venmo 打赏


---|---

[LeetCode] 7. Reverse Integer


请点击下方图片观看讲解视频
Click below image to watch YouTube Video
Video

Given a signed 32-bit integer x, return x with its digits reversed. If reversing x causes the value to go outside the signed 32-bit integer range [-2^31, 2^31 - 1], then return 0.

Assume the environment does not allow you to store 64-bit integers (signed or unsigned).

Example 1:

**Input:** x = 123
**Output:** 321

Example 2:

**Input:** x = -123
**Output:** -321

Example 3:

**Input:** x = 120
**Output:** 21

Constraints:

  • -2^31 <= x <= 2^31 - 1

翻转数字问题需要注意的就是溢出问题,看了许多网上的解法,由于之前的 OJ 没有对溢出进行测试,所以网上很多人的解法没有处理溢出问题也能通过 OJ。现在 OJ 更新了溢出测试,所以还是要考虑到。为什么会存在溢出问题呢,由于int型的数值范围是 -2147483648~2147483647, 那么如果要翻转 1000000009 这个在范围内的数得到 9000000001,而翻转后的数就超过了范围。博主最开始的想法是,用 long 型数据,其数值范围为 -9223372036854775808~9223372036854775807, 远大于 int 型这样就不会出现溢出问题。但实际上 OJ 给出的官方解答并不需要使用 long,一看比自己的写的更精简一些,它没有特意处理正负号,仔细一想,果然正负号不影响计算,而且没有用 long 型数据,感觉写的更好一些,那么就贴出来吧:

解法一:

class Solution {
public:
    int reverse(int x) {
        int res = 0;
        while (x != 0) {
            if (abs(res) > INT_MAX / 10) return 0;
            res = res * 10 + x % 10;
            x /= 10;
        }
        return res;
    }
};

在贴出答案的同时,OJ 还提了一个问题 To check for overflow/underflow, we could check if ret > 214748364 or ret < –214748364 before multiplying by 10. On the other hand, we do not need to check if ret == 214748364, why? (214748364 即为 INT_MAX / 10)

为什么不用 check 是否等于 214748364 呢,因为输入的x也是一个整型数,所以x的范围也应该在 -2147483648~2147483647 之间,那么x的第一位只能是1或者2,翻转之后 res 的最后一位只能是1或2,所以 res 只能是 2147483641 或 2147483642 都在 int 的范围内。但是它们对应的x为 1463847412 和 2463847412,后者超出了数值范围。所以当过程中 res 等于 214748364 时, 输入的x只能为 1463847412, 翻转后的结果为 2147483641,都在正确的范围内,所以不用 check。

我们也可以用 long 型变量保存计算结果,最后返回的时候判断是否在 int 返回内,但其实题目中说了只能存整型的变量,所以这种方法就只能当个思路扩展了,参见代码如下:

解法二:

class Solution {
public:
    int reverse(int x) {
        long res = 0;
        while (x != 0) {
            res = 10 * res + x % 10;
            x /= 10;
        }
        return (res > INT_MAX || res < INT_MIN) ? 0 : res;
    }
};

Github 同步地址:

#7

类似题目:

String to Integer (atoi)

Reverse Bits

参考资料:

https://leetcode.com/problems/reverse-integer/

https://leetcode.com/problems/reverse-integer/discuss/4060/My-accepted-15-lines-of-code-for-Java

https://leetcode.com/problems/reverse-integer/discuss/4056/Very-Short-(7-lines)-and-Elegant-Solution

LeetCode All in One 题目讲解汇总(持续更新中...)

(欢迎加入博主的知识星球,博主将及时答疑解惑,并分享刷题经验与总结,快快加入吧~)

知识星球 喜欢请点赞,疼爱请打赏❤️~.~

微信打赏

|

Venmo 打赏


---|---

[LeetCode] 11. Container With Most Water


请点击下方图片观看讲解视频
Click below image to watch YouTube Video
Video

You are given an integer array height of length n. There are n vertical lines drawn such that the two endpoints of the ith line are (i, 0) and (i, height[i]).

Find two lines that together with the x-axis form a container, such that the container contains the most water.

Return the maximum amount of water a container can store.

Notice that you may not slant the container.

Example 1:

**Input:** height = [1,8,6,2,5,4,8,3,7]
**Output:** 49
**Explanation:** The above vertical lines are represented by array [1,8,6,2,5,4,8,3,7]. In this case, the max area of water (blue section) the container can contain is 49.

Example 2:

**Input:** height = [1,1]
**Output:** 1

Constraints:

  • n == height.length
  • 2 <= n <= 105
  • 0 <= height[i] <= 104

这道求装最多水的容器的题和那道 Trapping Rain Water 很类似,但又有些不同,那道题让求整个能收集雨水的量,这道只是让求最大的一个的装水量,而且还有一点不同的是,那道题容器边缘不能算在里面,而这道题却可以算,相比较来说还是这道题容易一些,这里需要定义i和j两个指针分别指向数组的左右两端,然后两个指针向中间搜索,每移动一次算一个值和结果比较取较大的,容器装水量的算法是找出左右两个边缘中较小的那个乘以两边缘的距离,代码如下:

C++ 解法一:

class Solution {
public:
    int maxArea(vector<int>& height) {
        int res = 0, i = 0, j = height.size() - 1;
        while (i < j) {
            res = max(res, min(height[i], height[j]) * (j - i));
            height[i] < height[j] ? ++i : --j;
        }
        return res;
    }
};

Java 解法一:

public class Solution {
    public int maxArea(int[] height) {
        int res = 0, i = 0, j = height.length - 1;
        while (i < j) {
            res = Math.max(res, Math.min(height[i], height[j]) * (j - i));
            if (height[i] < height[j]) ++i;
            else --j;
        }
        return res;
    }
}

这里需要注意的是,由于 Java 中的三元运算符 A?B:C 必须须要有返回值,所以只能用 if..else.. 来替换,不知道 Java 对于三元运算符这么严格的限制的原因是什么。

下面这种方法是对上面的方法进行了小幅度的优化,对于相同的高度们直接移动i和j就行了,不再进行计算容量了,参见代码如下:

C++ 解法二:

class Solution {
public:
    int maxArea(vector<int>& height) {
        int res = 0, i = 0, j = height.size() - 1;
        while (i < j) {
            int h = min(height[i], height[j]);
            res = max(res, h * (j - i));
            while (i < j && h == height[i]) ++i;
            while (i < j && h == height[j]) --j;
        }
        return res;
    }
};

Java 解法二:

public class Solution {
    public int maxArea(int[] height) {
        int res = 0, i = 0, j = height.length - 1;
        while (i < j) {
            int h = Math.min(height[i], height[j]);
            res = Math.max(res, h * (j - i));
            while (i < j && h == height[i]) ++i;
            while (i < j && h == height[j]) --j;
        }
        return res;
    }
}

Github 同步地址:

#11

类似题目:

Trapping Rain Water

参考资料:

https://leetcode.com/problems/container-with-most-water/

https://leetcode.com/problems/container-with-most-water/discuss/6090/Simple-and-fast-C%2B%2BC-with-explanation

https://leetcode.com/problems/container-with-most-water/discuss/6091/Easy-Concise-Java-O(N)-Solution-with-Proof-and-Explanation

LeetCode All in One 题目讲解汇总(持续更新中...)

(欢迎加入博主的知识星球,博主将及时答疑解惑,并分享刷题经验与总结,快快加入吧~)

知识星球 喜欢请点赞,疼爱请打赏❤️~.~

微信打赏

|

Venmo 打赏


---|---

[LeetCode] 2. Add Two Numbers


请点击下方图片观看讲解视频
Click below image to watch YouTube Video
Video

You are given two non-empty linked lists representing two non-negative integers. The digits are stored in reverse order , and each of their nodes contains a single digit. Add the two numbers and return the sum as a linked list.

You may assume the two numbers do not contain any leading zero, except the number 0 itself.

Example 1:

**Input:** l1 = [2,4,3], l2 = [5,6,4]
**Output:** [7,0,8]
**Explanation:** 342 + 465 = 807.

Example 2:

**Input:** l1 = [0], l2 = [0]
**Output:** [0]

Example 3:

**Input:** l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9]
**Output:** [8,9,9,9,0,0,0,1]

Constraints:

  • The number of nodes in each linked list is in the range [1, 100].
  • 0 <= Node.val <= 9
  • It is guaranteed that the list represents a number that does not have leading zeros.

这道并不是什么难题,算法很简单,链表的数据类型也不难,就是建立一个新链表,然后把输入的两个链表从头往后撸,每两个相加,添加一个新节点到新链表后面。为了避免两个输入链表同时为空,我们建立一个 dummy 结点,将两个结点相加生成的新结点按顺序加到 dummy 结点之后,由于 dummy 结点本身不能变,所以用一个指针 cur 来指向新链表的最后一个结点。好,可以开始让两个链表相加了,这道题好就好在最低位在链表的开头,所以可以在遍历链表的同时按从低到高的顺序直接相加。while 循环的条件两个链表中只要有一个不为空行,由于链表可能为空,所以在取当前结点值的时候,先判断一下,若为空则取0,否则取结点值。然后把两个结点值相加,同时还要加上进位 carry。然后更新 carry,直接 sum/10 即可,然后以 sum%10 为值建立一个新结点,连到 cur 后面,然后 cur 移动到下一个结点。之后再更新两个结点,若存在,则指向下一个位置。while 循环退出之后,最高位的进位问题要最后特殊处理一下,若 carry 为1,则再建一个值为1的结点,代码如下:

C++ 解法:

class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        ListNode *dummy = new ListNode(-1), *cur = dummy;
        int carry = 0;
        while (l1 || l2) {
            int val1 = l1 ? l1->val : 0;
            int val2 = l2 ? l2->val : 0;
            int sum = val1 + val2 + carry;
            carry = sum / 10;
            cur->next = new ListNode(sum % 10);
            cur = cur->next;
            if (l1) l1 = l1->next;
            if (l2) l2 = l2->next;
        }
        if (carry) cur->next = new ListNode(1);
        return dummy->next;
    }
};

Java 解法:

public class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode dummy = new ListNode(-1);
        ListNode cur = dummy;
        int carry = 0;
        while (l1 != null || l2 != null) {
            int d1 = l1 == null ? 0 : l1.val;
            int d2 = l2 == null ? 0 : l2.val;
            int sum = d1 + d2 + carry;
            carry = sum >= 10 ? 1 : 0;
            cur.next = new ListNode(sum % 10);
            cur = cur.next;
            if (l1 != null) l1 = l1.next;
            if (l2 != null) l2 = l2.next;
        }
        if (carry == 1) cur.next = new ListNode(1);
        return dummy.next;
    }
}

在 CareerCup 上的这道题还有个 Follow Up,把链表存的数字方向变了,原来是表头存最低位,现在是表头存最高位,请参见我的另一篇博客 2.5 Add Two Numbers 两个数字相加

Github 同步地址:

#2

类似题目:

Multiply Strings

Add Binary

Sum of Two Integers

Add Strings

Add Two Numbers II

参考资料:

https://leetcode.com/problems/add-two-numbers/

https://leetcode.com/problems/add-two-numbers/discuss/997/c%2B%2B-Sharing-my-11-line-c%2B%2B-solution-can-someone-make-it-even-more-concise

LeetCode All in One 题目讲解汇总(持续更新中...)

(欢迎加入博主的知识星球,博主将及时答疑解惑,并分享刷题经验与总结,快快加入吧~)

知识星球 喜欢请点赞,疼爱请打赏❤️~.~

微信打赏

|

Venmo 打赏


---|---

[LeetCode] 16. 3Sum Closest


请点击下方图片观看讲解视频
Click below image to watch YouTube Video
Video

Given an integer array nums of length n and an integer target, find three integers in nums such that the sum is closest to target.

Return the sum of the three integers.

You may assume that each input would have exactly one solution.

Example 1:

**Input:** nums = [-1,2,1,-4], target = 1
**Output:** 2
**Explanation:** The sum that is closest to the target is 2. (-1 + 2 + 1 = 2).

Example 2:

**Input:** nums = [0,0,0], target = 1
**Output:** 0
**Explanation:** The sum that is closest to the target is 0. (0 + 0 + 0 = 0). 

Constraints:

  • 3 <= nums.length <= 500
  • -1000 <= nums[i] <= 1000
  • -104 <= target <= 104

这道题让我们求最接近给定值的三数之和,是在之前那道 3Sum 的基础上又增加了些许难度,那么这道题让返回这个最接近于给定值的值,即要保证当前三数和跟给定值之间的差的绝对值最小,所以需要定义一个变量 diff 用来记录差的绝对值,然后还是要先将数组排个序,然后开始遍历数组,思路跟那道三数之和很相似,都是先确定一个数,然后用两个指针 left 和 right 来滑动寻找另外两个数,每确定两个数,求出此三数之和,然后算和给定值的差的绝对值存在 newDiff 中,然后和 diff 比较并更新 diff 和结果 closest 即可,代码如下:

class Solution {
public:
    int threeSumClosest(vector<int>& nums, int target) {
        int closest = nums[0] + nums[1] + nums[2], n = nums.size();
        int diff = abs(closest - target);
        sort(nums.begin(), nums.end());
        for (int i = 0; i < n - 2; ++i) {
            if (i > 0 && nums[i] == nums[i - 1]) continue;
            int left = i + 1, right = n - 1;
            while (left < right) {
                int sum = nums[i] + nums[left] + nums[right];
                int newDiff = abs(sum - target);
                if (diff > newDiff) {
                    diff = newDiff;
                    closest = sum;
                }
                if (sum < target) ++left;
                else --right;
            }
        }
        return closest;
    }
};

Github 同步地址:

#16

类似题目:

3Sum Smaller

3Sum

参考资料:

https://leetcode.com/problems/3sum-closest/

https://leetcode.com/problems/3sum-closest/discuss/7883/C%2B%2B-solution-O(n2)-using-sort

https://leetcode.com/problems/3sum-closest/discuss/7872/Java-solution-with-O(n2)-for-reference

LeetCode All in One 题目讲解汇总(持续更新中...)

(欢迎加入博主的知识星球,博主将及时答疑解惑,并分享刷题经验与总结,快快加入吧~)

知识星球 喜欢请点赞,疼爱请打赏❤️~.~

微信打赏

|

Venmo 打赏


---|---

[LeetCode] 14. Longest Common Prefix


请点击下方图片观看讲解视频
Click below image to watch YouTube Video
Video

Write a function to find the longest common prefix string amongst an array of strings.

If there is no common prefix, return an empty string "".

Example 1:

**Input:** strs = ["flower","flow","flight"]
**Output:** "fl"

Example 2:

**Input:** strs = ["dog","racecar","car"]
**Output:** ""
**Explanation:** There is no common prefix among the input strings.

Constraints:

  • 1 <= strs.length <= 200
  • 0 <= strs[i].length <= 200
  • strs[i] consists of only lowercase English letters.

这道题让我们求一系列字符串的共同前缀,没有什么特别的技巧,无脑查找即可,定义两个变量i和j,其中j是遍历搜索字符串中的字符,i是遍历字符串集中的每个字符串。这里将单词上下排好,则相当于一个各行长度有可能不相等的二维数组,遍历顺序和一般的横向逐行遍历不同,而是采用纵向逐列遍历,在遍历的过程中,如果某一行没有了,说明其为最短的单词,因为共同前缀的长度不能长于最短单词,所以此时返回已经找出的共同前缀。每次取出第一个字符串的某一个位置的单词,然后遍历其他所有字符串的对应位置看是否相等,如果有不满足的直接返回 res,如果都相同,则将当前字符存入结果,继续检查下一个位置的字符,参见代码如下:

C++ 解法一:

class Solution {
public:
    string longestCommonPrefix(vector<string>& strs) {
        if (strs.empty()) return "";
        string res = "";
        for (int j = 0; j < strs[0].size(); ++j) {
            char c = strs[0][j];
            for (int i = 1; i < strs.size(); ++i) {
                if (j >= strs[i].size() || strs[i][j] != c) {
                    return res;
                }
            }
            res.push_back(c);
        }
        return res;
    }
};

Java 解法一:

public class Solution {
    public String longestCommonPrefix(String[] strs) {
        if (strs == null || strs.length == 0) return "";
        String res = new String();
        for (int j = 0; j < strs[0].length(); ++j) {
            char c = strs[0].charAt(j);
            for (int i = 1; i < strs.length; ++i) {
                if (j >= strs[i].length() || strs[i].charAt(j) != c) {
                    return res;
                }
            }
            res += Character.toString(c);
        }
        return res;
    }
}

我们可以对上面的方法进行适当精简,如果发现当前某个字符和第一个字符串对应位置的字符不相等,说明不会再有更长的共同前缀了,直接通过用 substr 的方法直接取出共同前缀的子字符串。如果遍历结束前没有返回结果的话,说明第一个单词就是公共前缀,返回为结果即可。代码如下:

C++ 解法二:

class Solution {
public:
    string longestCommonPrefix(vector<string>& strs) {
        if (strs.empty()) return "";
        for (int j = 0; j < strs[0].size(); ++j) {
            for (int i = 0; i < strs.size(); ++i) {
                if (j >= strs[i].size() || strs[i][j] != strs[0][j]) {
                    return strs[i].substr(0, j);
                }
            }
        }
        return strs[0];
    }
};

Java 解法二:

class Solution {
    public String longestCommonPrefix(String[] strs) {
        if (strs == null || strs.length == 0) return "";
        for (int j = 0; j < strs[0].length(); ++j) {
            for (int i = 0; i < strs.length; ++i) {
                if (j >= strs[i].length() || strs[i].charAt(j) != strs[0].charAt(j)) {
                    return strs[i].substring(0, j); 
                }   
            }
        }
        return strs[0];
    }
}

我们再来看一种解法,这种方法给输入字符串数组排了个序,想想这样做有什么好处?既然是按字母顺序排序的话,那么有共同字母多的两个字符串会被排到一起,而跟大家相同的字母越少的字符串会被挤到首尾两端,那么如果有共同前缀的话,一定会出现在首尾两端的字符串中,所以只需要找首尾字母串的共同前缀即可。比如例子1排序后为 ["flight", "flow", "flower"],例子2排序后为 ["cat", "dog", "racecar"],虽然例子2没有共同前缀,但也可以认为共同前缀是空串,且出现在首尾两端的字符串中。由于是按字母顺序排的,而不是按长度,所以首尾字母的长度关系不知道,为了防止溢出错误,只遍历而这种较短的那个的长度,找出共同前缀返回即可,参见代码如下:

C++ 解法三:

class Solution {
public:
    string longestCommonPrefix(vector<string>& strs) {
        if (strs.empty()) return "";
        sort(strs.begin(), strs.end());
        int i = 0, len = min(strs[0].size(), strs.back().size());
        while (i < len && strs[0][i] == strs.back()[i]) ++i;
        return strs[0].substr(0, i);
    }
};

Java 解法三:

class Solution {
    public String longestCommonPrefix(String[] strs) {
        if (strs == null || strs.length == 0) return "";
        Arrays.sort(strs);
        int i = 0, len = Math.min(strs[0].length(), strs[strs.length - 1].length());
        while (i < len && strs[0].charAt(i) == strs[strs.length - 1].charAt(i)) i++;
        return strs[0].substring(0, i);
    }
}

Github 同步地址:

#14

参考资料:

https://leetcode.com/problems/longest-common-prefix

https://leetcode.com/problems/longest-common-prefix/discuss/6910/Java-code-with-13-lines

https://leetcode.com/problems/longest-common-prefix/discuss/6940/Java-We-Love-Clear-Code!

https://leetcode.com/problems/longest-common-prefix/discuss/6926/Accepted-c%2B%2B-6-lines-4ms

LeetCode All in One 题目讲解汇总(持续更新中...)

(欢迎加入博主的知识星球,博主将及时答疑解惑,并分享刷题经验与总结,快快加入吧~)

知识星球 喜欢请点赞,疼爱请打赏❤️~.~

微信打赏

|

Venmo 打赏


---|---

[LeetCode] 20. Valid Parentheses


请点击下方图片观看讲解视频
Click below image to watch YouTube Video
Video

Given a string s containing just the characters '(', ')', '{', '}', '[' and ']', determine if the input string is valid.

An input string is valid if:

  1. Open brackets must be closed by the same type of brackets.
  2. Open brackets must be closed in the correct order.
  3. Every close bracket has a corresponding open bracket of the same type.

Example 1:

**Input:** s = "()"
**Output:** true

Example 2:

**Input:** s = "()[]{}"
**Output:** true

Example 3:

**Input:** s = "(]"
**Output:** false

Constraints:

  • 1 <= s.length <= 104
  • s consists of parentheses only '()[]{}'.

这道题让我们验证输入的字符串是否为括号字符串,包括大括号,中括号和小括号。这里需要用一个栈,开始遍历输入字符串,如果当前字符为左半边括号时,则将其压入栈中,如果遇到右半边括号时,若此时栈为空,则直接返回 false,如不为空,则取出栈顶元素,若为对应的左半边括号,则继续循环,反之返回 false,代码如下:

解法一:

class Solution {
public:
    bool isValid(string s) {
        stack<char> st;
        for (int i = 0; i < s.size(); ++i) {
            if (s[i] == '(' || s[i] == '[' || s[i] == '{') {
                st.push(s[i]);
            } else {
                if (st.empty()) return false;
                if (s[i] == ')' && st.top() != '(') return false;
                if (s[i] == ']' && st.top() != '[') return false;
                if (s[i] == '}' && st.top() != '{') return false;
                st.pop();
            }
        }
        return st.empty();
    }
}; 

再来看一种写的更加简洁的方法,这里用个小 trick,之前是当遇到左括号时,把左括号压入栈,这样在出栈检测的时候,得判断当前遇到的右括号是哪种括号,是否跟栈顶的左括号匹配。而假如在压入栈的时候,我们直接将左括号对应的右括号压入栈,则在出栈检测的时候,就直接可以比较是否和栈顶元素相等了,因为都是右括号,这样写起来能相对简单一点,但是整体的思路还是相同的,参见代码如下:

解法二:

class Solution {
public:
    bool isValid(string s) {
        stack<char> st;
        for (char c : s) {
            if (c == '(') st.push(')');
            else if (c == '{') st.push('}');
            else if (c == '[') st.push(']');
            else if (st.empty() || st.top() != c) return false;
            else st.pop();
        }
        return st.empty();
    }
}; 

Github 同步地址:

#20

类似题目:

Remove Invalid Parentheses

Different Ways to Add Parentheses

Longest Valid Parentheses

Generate Parentheses

Check If Word Is Valid After Substitutions

Check if a Parentheses String Can Be Valid

Move Pieces to Obtain a String

参考资料:

https://leetcode.com/problems/valid-parentheses/

https://leetcode.com/problems/valid-parentheses/discuss/9178/Short-java-solution

https://leetcode.com/problems/valid-parentheses/discuss/9248/My-easy-to-understand-Java-Solution-with-one-stack

LeetCode All in One 题目讲解汇总(持续更新中...)

(欢迎加入博主的知识星球,博主将及时答疑解惑,并分享刷题经验与总结,快快加入吧~)

知识星球 喜欢请点赞,疼爱请打赏❤️~.~

微信打赏

|

Venmo 打赏


---|---

[LeetCode] 24. Swap Nodes in Pairs


请点击下方图片观看讲解视频
Click below image to watch YouTube Video
Video

Given a linked list, swap every two adjacent nodes and return its head. You must solve the problem without modifying the values in the list's nodes (i.e., only nodes themselves may be changed.)

Example 1:

**Input:** head = [1,2,3,4]
**Output:** [2,1,4,3]

Example 2:

**Input:** head = []
**Output:** []

Example 3:

**Input:** head = [1]
**Output:** [1]

Constraints:

  • The number of nodes in the list is in the range [0, 100].
  • 0 <= Node.val <= 100

这道题不算难,是基本的链表操作题,我们可以分别用递归和迭代来实现。对于迭代实现,还是需要建立 dummy 节点,注意在连接节点的时候,最好画个图,以免把自己搞晕了,参见代码如下:

解法一:

class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        ListNode *dummy = new ListNode(-1), *pre = dummy;
        dummy->next = head;
        while (pre->next && pre->next->next) {
            ListNode *t = pre->next->next;
            pre->next->next = t->next;
            t->next = pre->next;
            pre->next = t;
            pre = t->next;
        }
        return dummy->next;
    }
};

递归的写法就更简洁了,实际上利用了回溯的**,递归遍历到链表末尾,然后先交换末尾两个,然后依次往前交换:

解法二:

class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        if (!head || !head->next) return head;
        ListNode *t = head->next;
        head->next = swapPairs(head->next->next);
        t->next = head;
        return t;
    }
};

Github 同步地址:

#24

类似题目:

Reverse Nodes in k-Group

Swapping Nodes in a Linked List

参考资料:

https://leetcode.com/problems/swap-nodes-in-pairs

https://leetcode.com/problems/swap-nodes-in-pairs/discuss/11030/My-accepted-java-code.-used-recursion.

LeetCode All in One 题目讲解汇总(持续更新中...)

(欢迎加入博主的知识星球,博主将及时答疑解惑,并分享刷题经验与总结,快快加入吧~)

知识星球 喜欢请点赞,疼爱请打赏❤️~.~

微信打赏

|

Venmo 打赏


---|---

[LeetCode] 13. Roman to Integer


请点击下方图片观看讲解视频
Click below image to watch YouTube Video
Video

Roman numerals are represented by seven different symbols: I, V, X, L, C, D and M.

Symbol       Value
I             1
V             5
X             10
L             50
C             100
D             500
M             1000

For example, two is written as II in Roman numeral, just two one's added together. Twelve is written as, XII, which is simply X+ II. The number twenty seven is written as XXVII, which is XX + V + II.

Roman numerals are usually written largest to smallest from left to right. However, the numeral for four is not IIII. Instead, the number four is written as IV. Because the one is before the five we subtract it making four. The same principle applies to the number nine, which is written as IX. There are six instances where subtraction is used:

  • I can be placed before V (5) and X(10) to make 4 and 9.
  • X can be placed before L (50) and C (100) to make 40 and 90.
  • C can be placed before D (500) and M (1000) to make 400 and 900.

Given a roman numeral, convert it to an integer. Input is guaranteed to be within the range from 1 to 3999.

Example 1:

Input: "III"
Output: 3

Example 2:

Input: "IV"
Output: 4

Example 3:

Input: "IX"
Output: 9

Example 4:

Input: "LVIII"
Output: 58
Explanation: L = 50, V= 5, III = 3.

Example 5:

Input: "MCMXCIV"
Output: 1994
Explanation: M = 1000, CM = 900, XC = 90 and IV = 4.

Constraints:

  • 1 <= s.length <= 15
  • s contains only the characters ('I', 'V', 'X', 'L', 'C', 'D', 'M').
  • It is guaranteed that s is a valid roman numeral in the range [1, 3999].

罗马数 转化成数字问题,我们需要对于 罗马数字 很熟悉才能完成转换。以下截自百度百科:

罗马 数字是最早的数字表示方式,比阿拉伯数字早2000多年,起源于罗马。

如今我们最常见的罗马数字就是钟表的表盘符号: Ⅰ,Ⅱ,Ⅲ,Ⅳ(IIII),Ⅴ,Ⅵ,Ⅶ,Ⅷ,Ⅸ,Ⅹ,Ⅺ,Ⅻ……

对应阿拉伯数字(就是现在国际通用的数字),就是1,2,3,4,5,6,7,8,9,10,11,12。(注: 阿拉伯数字 其实是古代印度人发明的,后来由阿拉伯人传入欧洲,被欧洲人误称为阿拉伯数字。)

I - 1

V - 5

X - 10

L - 50

C - 100

D - 500

M - 1000

1、相同的数字连写,所表示的数等于这些数字相加得到的数,如:Ⅲ = 3;

2、小的数字在大的数字的右边,所表示的数等于这些数字相加得到的数, 如:Ⅷ = 8;Ⅻ = 12;

3、小的数字,(限于Ⅰ、X 和C)在大的数字的左边,所表示的数等于大数减小数得到的数,如:Ⅳ= 4;Ⅸ= 9;

4、正常使用时,连写的数字重复不得超过三次。(表盘上的四点钟“IIII”例外)

5、在一个数的上面画一条横线,表示这个数扩大1000倍。

有几条须注意掌握:

1、基本数字Ⅰ、X 、C 中的任何一个,自身连用构成数目,或者放在大数的右边连用构成数目,都不能超过三个;放在大数的左边只能用一个。

2、不能把基本数字V 、L 、D 中的任何一个作为小数放在大数的左边采用相减的方法构成数目;放在大数的右边采用相加的方式构成数目,只能使用一个。

3、V 和X 左边的小数字只能用Ⅰ。

4、L 和C 左边的小数字只能用X。

5、D 和M 左边的小数字只能用C。

而这道题好就好在没有让我们来验证输入字符串是不是罗马数字,这样省掉不少功夫。需要用到 HashMap 数据结构,来将罗马数字的字母转化为对应的整数值,因为输入的一定是罗马数字,那么只要考虑两种情况即可:

第一,如果当前数字是最后一个数字,或者之后的数字比它小的话,则加上当前数字。

第二,其他情况则减去这个数字。

解法一:

class Solution {
public:
    int romanToInt(string s) {
        int res = 0;
        unordered_map<char, int> m{{'I', 1}, {'V', 5}, {'X', 10}, {'L', 50}, {'C', 100}, {'D', 500}, {'M', 1000}};
        for (int i = 0; i < s.size(); ++i) {
            int val = m[s[i]];
            if (i == s.size() - 1 || m[s[i+1]] <= m[s[i]]) res += val;
            else res -= val;
        }
        return res;
    }
};

我们也可以每次跟前面的数字比较,如果小于等于前面的数字,先加上当前的数字,比如 "VI",第二个字母 'I' 小于第一个字母 'V',所以要加1。如果大于的前面的数字,加上当前的数字减去二倍前面的数字,这样可以把在上一个循环多加数减掉,比如 "IX",我们在 i=0 时,加上了第一个字母 'I' 的值,此时结果 res 为1。当 i=1 时,字母 'X' 大于前一个字母 'I',这说明前面的1是要减去的,而由于前一步不但没减,还多加了个1,所以此时要减去2倍的1,就是减2,所以才能得到9,整个过程是 res = 1 + 10 - 2 = 9,参见代码如下:

解法二:

class Solution {
public:
    int romanToInt(string s) {
        int res = 0;
        unordered_map<char, int> m{{'I', 1}, {'V', 5}, {'X', 10}, {'L', 50}, {'C', 100}, {'D', 500}, {'M', 1000}};
        for (int i = 0; i < s.size(); ++i) {
            if (i == 0 || m[s[i]] <= m[s[i - 1]]) res += m[s[i]];
            else res += m[s[i]] - 2 * m[s[i - 1]];
        }
        return res;
    }
};

Github 同步地址:

#13

类似题目:

Integer to Roman

参考资料:

https://leetcode.com/problems/roman-to-integer/

https://leetcode.com/problems/roman-to-integer/discuss/6547/Clean-O(n)-c%2B%2B-solution

https://leetcode.com/problems/roman-to-integer/discuss/6529/My-solution-for-this-question-but-I-don't-know-is-there-any-easier-way

LeetCode All in One 题目讲解汇总(持续更新中...)

(欢迎加入博主的知识星球,博主将及时答疑解惑,并分享刷题经验与总结,快快加入吧~)

知识星球 喜欢请点赞,疼爱请打赏❤️~.~

微信打赏

|

Venmo 打赏


---|---

[LeetCode] 9. Palindrome Number


请点击下方图片观看讲解视频
Click below image to watch YouTube Video
Video

Given an integer x, return true ifx is a _ palindrome_ , andfalse otherwise.

Example 1:

**Input:** x = 121
**Output:** true
**Explanation:** 121 reads as 121 from left to right and from right to left.

Example 2:

**Input:** x = -121
**Output:** false
**Explanation:** From left to right, it reads -121. From right to left, it becomes 121-. Therefore it is not a palindrome.

Example 3:

**Input:** x = 10
**Output:** false
**Explanation:** Reads 01 from right to left. Therefore it is not a palindrome.

Constraints:

  • -231 <= x <= 231 - 1

Follow up: Could you solve it without converting the integer to a string?

这道验证回文数字的题如果将数字转为字符串,就变成了验证回文字符串的题,没啥难度了,我们就直接来做 follow up 吧,不能转为字符串,而是直接对整数进行操作,可以利用取整和取余来获得想要的数字,比如 1221 这个数字,如果 计算 1221 / 1000, 则可得首位1, 如果 1221 % 10, 则可得到末尾1,进行比较,然后把中间的 22 取出继续比较。代码如下:

解法一:

class Solution {
public:
    bool isPalindrome(int x) {
        if (x < 0) return false;
        int div = 1;
        while (x / div >= 10) div *= 10;
        while (x > 0) {
            int left = x / div;
            int right = x % 10;
            if (left != right) return false;
            x = (x % div) / 10;
            div /= 100;
        }
        return true;
    }
};

再来看一种很巧妙的解法,还是首先判断x是否为负数,这里可以用一个小 trick,因为整数的最高位不能是0,所以回文数的最低位也不能为0,数字0除外,所以如果发现某个正数的末尾是0了,也直接返回 false 即可。好,下面来看具体解法,要验证回文数,那么就需要看前后半段是否对称,如果把后半段翻转一下,就看和前半段是否相等就行了。所以做法就是取出后半段数字,进行翻转,具体做法是,每次通过对 10 取余,取出最低位的数字,然后加到取出数的末尾,就是将 revertNum 乘以 10,再加上这个余数,这样翻转也就同时完成了,每取一个最低位数字,x都要自除以 10。这样当 revertNum 大于等于x的时候循环停止。由于回文数的位数可奇可偶,如果是偶数的话,那么 revertNum 就应该和x相等了;如果是奇数的话,那么最中间的数字就在 revertNum 的最低位上了,除以 10 以后应该和x是相等的,参见代码如下:

解法二:

class Solution {
public:
    bool isPalindrome(int x) {
        if (x < 0 || (x % 10 == 0 && x != 0)) return false;
        int revertNum = 0;
        while (x > revertNum) {
            revertNum = revertNum * 10 + x % 10;
            x /= 10;
        }
        return x == revertNum || x == revertNum / 10;
    }
};

下面这种解法由热心网友 zeeng 提供,如果是 palindrome,反转后仍是原数字,就不可能溢出,只要溢出一定不是 palindrome 返回 false 就行。可以参考 Reverse Integer 这题,直接调用 Reverse()。

解法三:

class Solution {
public:
    bool isPalindrome(int x) {
        if (x < 0 || (x % 10 == 0 && x != 0)) return false;
        return reverse(x) == x;
    }
    int reverse(int x) {
        int res = 0;
        while (x != 0) {
            if (res > INT_MAX / 10) return -1;
            res = res * 10 + x % 10;
            x /= 10;
        }
        return res;
    }
};

Github 同步地址:

#9

类似题目:

Palindrome Linked List

Reverse Integer

参考资料:

https://leetcode.com/problems/palindrome-number/

https://leetcode.com/problems/palindrome-number/discuss/5127/9-line-accepted-Java-code-without-the-need-of-handling-overflow

LeetCode All in One 题目讲解汇总(持续更新中...)

(欢迎加入博主的知识星球,博主将及时答疑解惑,并分享刷题经验与总结,快快加入吧~)

知识星球 喜欢请点赞,疼爱请打赏❤️~.~

微信打赏

|

Venmo 打赏


---|---

[LeetCode] 5. Longest Palindromic Substring


请点击下方图片观看讲解视频
Click below image to watch YouTube Video
Video

Given a string s, return the longest palindromic substring in s.

Example 1:

**Input:** s = "babad"
**Output:** "bab"
**Explanation:** "aba" is also a valid answer.

Example 2:

**Input:** s = "cbbd"
**Output:** "bb"

Constraints:

  • 1 <= s.length <= 1000
  • s consist of only digits and English letters.

这道题让我们求最长回文子串,首先说下什么是回文串,就是正读反读都一样的字符串,比如 "bob", "level", "noon" 等等。那么最长回文子串就是在一个字符串中的那个最长的回文子串。LeetCode 中关于回文串的题共有五道,除了这道,其他的四道为 Palindrome NumberValidate PalindromePalindrome PartitioningPalindrome Partitioning II,我们知道传统的验证回文串的方法就是两个两个的对称验证是否相等,那么对于找回文字串的问题,就要以每一个字符为中心,像两边扩散来寻找回文串,这个算法的时间复杂度是 O(n*n),可以通过 OJ,就是要注意奇偶情况,由于回文串的长度可奇可偶,比如 "bob" 是奇数形式的回文,"noon" 就是偶数形式的回文,两种形式的回文都要搜索,对于奇数形式的,我们就从遍历到的位置为中心,向两边进行扩散,对于偶数情况,我们就把当前位置和下一个位置当作偶数行回文的最中间两个字符,然后向两边进行搜索,参见代码如下:

解法一:

class Solution {
public:
    string longestPalindrome(string s) {
        if (s.size() < 2) return s;
        int n = s.size(), maxLen = 0, start = 0;
        for (int i = 0; i < n - 1; ++i) {
            searchPalindrome(s, i, i, start, maxLen);
            searchPalindrome(s, i, i + 1, start, maxLen);
        }
        return s.substr(start, maxLen);
    }
    void searchPalindrome(string s, int left, int right, int& start, int& maxLen) {
        while (left >= 0 && right < s.size() && s[left] == s[right]) {
            --left; ++right;
        }
        if (maxLen < right - left - 1) {
            start = left + 1;
            maxLen = right - left - 1;
        }
    }
};

我们也可以不使用子函数,直接在一个函数中搞定,我们还是要定义两个变量 start 和 maxLen,分别表示最长回文子串的起点跟长度,在遍历s中的字符的时候,我们首先判断剩余的字符数是否小于等于 maxLen 的一半,是的话表明就算从当前到末尾到子串是半个回文串,那么整个回文串长度最多也就是 maxLen,既然 maxLen 无法再变长了,计算这些就没有意义,直接在当前位置 break 掉就行了。否则就要继续判断,我们用两个变量 left 和 right 分别指向当前位置,然后我们先要做的是向右遍历跳过重复项,这个操作很必要,比如对于 noon,i在第一个o的位置,如果我们以o为最中心往两边扩散,是无法得到长度为4的回文串的,只有先跳过重复,此时left指向第一个o,right指向第二个o,然后再向两边扩散。而对于 bob,i在第一个o的位置时,无法向右跳过重复,此时 left 和 right 同时指向o,再向两边扩散也是正确的,所以可以同时处理奇数和偶数的回文串,之后的操作就是更新 maxLen 和 start 了,跟上面的操作一样,参见代码如下:

解法二:

class Solution {
public:
    string longestPalindrome(string s) {
        if (s.size() < 2) return s;
        int n = s.size(), maxLen = 0, start = 0;
        for (int i = 0; i < n;) {
            if (n - i <= maxLen / 2) break;
            int left = i, right = i;
            while (right < n - 1 && s[right + 1] == s[right]) ++right;
            i = right + 1;
            while (right < n - 1 && left > 0 && s[right + 1] == s[left - 1]) {
                ++right; --left;
            }
            if (maxLen < right - left + 1) {
                maxLen = right - left + 1;
                start = left;
            }
        }
        return s.substr(start, maxLen);
    }
};

此题还可以用动态规划 Dynamic Programming 来解,根 Palindrome Partitioning II 的解法很类似,我们维护一个二维数组 dp,其中 dp[i][j] 表示字符串区间 [i, j] 是否为回文串,当 i = j 时,只有一个字符,肯定是回文串,如果 i = j + 1,说明是相邻字符,此时需要判断 s[i] 是否等于 s[j],如果i和j不相邻,即 i - j >= 2 时,除了判断 s[i] 和 s[j] 相等之外,dp[i + 1][j - 1] 若为真,就是回文串,通过以上分析,可以写出递推式如下:

dp[i, j] = true if i == j

= s[i] == s[j] if j = i + 1

= s[i] == s[j] && dp[i + 1][j - 1] if j > i + 1

解法三:

class Solution {
public:
    string longestPalindrome(string s) {
        if (s.empty()) return "";
        int n = s.size(), left = 0, len = 1;
        vector<vector<bool>> dp(n, vector<bool>(n));     
        for (int i = 0; i < n; ++i) {
            dp[i][i] = true;
            for (int j = 0; j < i; ++j) {
                dp[j][i] = (s[i] == s[j] && (i - j < 2 || dp[j + 1][i - 1]));
                if (dp[j][i] && len < i - j + 1) {
                    len = i - j + 1;
                    left = j;
                }
            }
        }
        return s.substr(left, len);
    }
};

最后要来的就是大名鼎鼎的马拉车算法 Manacher's Algorithm,这个算法的神奇之处在于将时间复杂度提升到了 O(n) 这种逆天的地步,而算法本身也设计的很巧妙,很值得我们掌握,参见我另一篇专门介绍马拉车算法的博客 Manacher's Algorithm 马拉车算法,代码实现如下:

解法四:

class Solution {
public:
    string longestPalindrome(string s) {
        string t ="$#";
        for (int i = 0; i < s.size(); ++i) {
            t += s[i];
            t += '#';
        }
        vector<int> p(t.size());
        int id = 0, mx = 0, resId = 0, resMx = 0;
        for (int i = 1; i < t.size(); ++i) {
            p[i] = mx > i ? min(p[2 * id - i], mx - i) : 1;
            while (t[i + p[i]] == t[i - p[i]]) ++p[i];
            if (mx < i + p[i]) {
                mx = i + p[i];
                id = i;
            }
            if (resMx < p[i]) {
                resMx = p[i];
                resId = i;
            }
        }
        return s.substr((resId - resMx) / 2, resMx - 1);
    }
};

Github 同步地址:

#5

类似题目:

Shortest Palindrome

Palindrome Permutation

Palindrome Pairs

Longest Palindromic Subsequence

Palindromic Substrings

参考资料:

https://leetcode.com/problems/longest-palindromic-substring/

https://leetcode.com/problems/longest-palindromic-substring/discuss/2928/Very-simple-clean-java-solution

https://leetcode.com/problems/longest-palindromic-substring/discuss/2923/Simple-C%2B%2B-solution-(8ms-13-lines)

https://leetcode.com/problems/longest-palindromic-substring/discuss/2921/Share-my-Java-solution-using-dynamic-programming

LeetCode All in One 题目讲解汇总(持续更新中...)

(欢迎加入博主的知识星球,博主将及时答疑解惑,并分享刷题经验与总结,快快加入吧~)

知识星球 喜欢请点赞,疼爱请打赏❤️~.~

微信打赏

|

Venmo 打赏


---|---

[LeetCode] 26. Remove Duplicates from Sorted Array


请点击下方图片观看讲解视频
Click below image to watch YouTube Video
Video

Given an integer array nums sorted in non-decreasing order , remove the duplicates in-place such that each unique element appears only once. The relative order of the elements should be kept the same. Then return the number of unique elements innums.

Consider the number of unique elements of nums to be k, to get accepted, you need to do the following things:

  • Change the array nums such that the first k elements of nums contain the unique elements in the order they were present in nums initially. The remaining elements of nums are not important as well as the size of nums.
  • Return k.

Custom Judge:

The judge will test your solution with the following code:

int[] nums = [...]; // Input array
int[] expectedNums = [...]; // The expected answer with correct length


int k = removeDuplicates(nums); // Calls your implementation




assert k == expectedNums.length;  

for (int i = 0; i < k; i++) {  

assert nums[i] == expectedNums[i];  

}  

If all assertions pass, then your solution will be accepted.

Example 1:

**Input:** nums = [1,1,2]
**Output:** 2, nums = [1,2,_]
**Explanation:** Your function should return k = 2, with the first two elements of nums being 1 and 2 respectively.
It does not matter what you leave beyond the returned k (hence they are underscores).

Example 2:

**Input:** nums = [0,0,1,1,1,2,2,3,3,4]
**Output:** 5, nums = [0,1,2,3,4,_,_,_,_,_]
**Explanation:** Your function should return k = 5, with the first five elements of nums being 0, 1, 2, 3, and 4 respectively.
It does not matter what you leave beyond the returned k (hence they are underscores).

Constraints:

  • 1 <= nums.length <= 3 * 10^4
  • -100 <= nums[i] <= 100
  • nums is sorted in non-decreasing order.

这道题要我们从有序数组中去除重复项,和之前那道 Remove Duplicates from Sorted List 的题很类似,但是要简单一些,因为毕竟数组的值可以通过下标直接访问,而链表不行。那么这道题的解题思路是使用快慢指针来记录遍历的坐标,最开始时两个指针都指向第一个数字,如果两个指针指的数字相同,则快指针向前走一步,如果不同,则两个指针都向前走一步,这样当快指针走完整个数组后,慢指针当前的坐标加1就是数组中不同数字的个数,代码如下:

解法一:

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        int pre = 0, cur = 0, n = nums.size();
        while (cur < n) {
            if (nums[pre] == nums[cur]) ++cur;
            else nums[++pre] = nums[cur++];
        }
        return nums.empty() ? 0 : (pre + 1);
    }
};

我们也可以用 for 循环来写,这里的j就是上面解法中的 pre,i就是 cur,所以本质上都是一样的,参见代码如下:

解法二:

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        int j = 0, n = nums.size();
        for (int i = 0; i < n; ++i) {
            if (nums[i] != nums[j]) nums[++j] = nums[i];
        }
        return nums.empty() ? 0 : (j + 1);
    }
};

这里也可以换一种写法,用变量i表示当前覆盖到到位置,由于不能有重复数字,则只需要用当前数字 num 跟上一个覆盖到到数字 nums[i-1] 做个比较,只要 num 大,则一定不会有重复(前提是数组必须有序),参见代码如下:

解法三:

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        int i = 0;
        for (int num : nums) {
            if (i < 1 || num > nums[i - 1]) {
                nums[i++] = num;
            }
        }
        return i;
    }
};

Github 同步地址:

#26

类似题目:

Remove Duplicates from Sorted List

Remove Duplicates from Sorted List II

Remove Duplicates from Sorted Array II

Remove Element

Apply Operations to an Array

Sum of Distances

类似题目:

https://leetcode.com/problems/remove-duplicates-from-sorted-array/

https://leetcode.com/problems/remove-duplicates-from-sorted-array/discuss/11757/My-Solution-%3A-Time-O(n)-Space-O(1)

LeetCode All in One 题目讲解汇总(持续更新中...)

(欢迎加入博主的知识星球,博主将及时答疑解惑,并分享刷题经验与总结,快快加入吧~)

知识星球 喜欢请点赞,疼爱请打赏❤️~.~

微信打赏

|

Venmo 打赏


---|---

[LeetCode] 21. Merge Two Sorted Lists


请点击下方图片观看讲解视频
Click below image to watch YouTube Video
Video

You are given the heads of two sorted linked lists list1 and list2.

Merge the two lists into one sorted list. The list should be made by splicing together the nodes of the first two lists.

Return the head of the merged linked list.

Example 1:

**Input:** list1 = [1,2,4], list2 = [1,3,4]
**Output:** [1,1,2,3,4,4]

Example 2:

**Input:** list1 = [], list2 = []
**Output:** []

Example 3:

**Input:** list1 = [], list2 = [0]
**Output:** [0]

Constraints:

  • The number of nodes in both lists is in the range [0, 50].
  • -100 <= Node.val <= 100
  • Both list1 and list2 are sorted in non-decreasing order.

这道混合插入有序链表和博主之前那篇混合插入有序数组非常的相似 Merge Sorted Array,仅仅是数据结构由数组换成了链表而已,代码写起来反而更简洁。具体**就是新建一个链表,然后比较两个链表中的元素值,把较小的那个链到新链表中,由于两个输入链表的长度可能不同,所以最终会有一个链表先完成插入所有元素,则直接另一个未完成的链表直接链入新链表的末尾。代码如下:

C++ 解法一:

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        ListNode *dummy = new ListNode(-1), *cur = dummy;
        while (list1 && list2) {
            if (list1->val < list2->val) {
                cur->next = list1;
                list1 = list1->next;
            } else {
                cur->next = list2;
                list2 = list2->next;
            }
            cur = cur->next;
        }
        cur->next = list1 ? list1 : list2;
        return dummy->next;
    }
};

Java 解法一:

public class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        ListNode dummy = new ListNode(-1), cur = dummy;
        while (list1 != null && list2 != null) {
            if (list1.val < list2.val) {
                cur.next = list1;
                list1 = list1.next;
            } else {
                cur.next = list2;
                list2 = list2.next;
            }
            cur = cur.next;
        }
        cur.next = (list1 != null) ? list1 : list2;
        return dummy.next;
    }
}

下面来看递归的写法,当某个链表为空了,就返回另一个。然后核心还是比较当前两个节点值大小,如果 l1 的小,那么对于 l1 的下一个节点和 l2 调用递归函数,将返回值赋值给 l1.next,然后返回 l1;否则就对于 l2 的下一个节点和 l1 调用递归函数,将返回值赋值给 l2.next,然后返回 l2,参见代码如下:

C++ 解法二:

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        if (!list1) return list2;
        if (!list2) return list1;
        if (list1->val < list2->val) {
            list1->next = mergeTwoLists(list1->next, list2);
            return list1;
        } else {
            list2->next = mergeTwoLists(list1, list2->next);
            return list2;
        }
    }
};

Java 解法二:

public class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        if (list1 == null) return list2;
        if (list2 == null) return list1;
        if (list1.val < list2.val) {
            list1.next = mergeTwoLists(list1.next, list2);
            return list1;
        } else {
            list2.next = mergeTwoLists(list1, list2.next);
            return list2;
        }
    }
}

下面这种递归的写法去掉了 if 从句,看起来更加简洁一些,但是思路并没有什么不同:

C++ 解法三:

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        if (!list1) return list2;
        if (!list2) return list1;
        ListNode *head = list1->val < list2->val ? list1 : list2;
        ListNode *nonhead = list1->val < list2->val ? list2 : list1;
        head->next = mergeTwoLists(head->next, nonhead);
        return head;
    }
};

Java 解法三:

public class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        if (list1 == null) return list2;
        if (list2 == null) return list1;
        ListNode head = (list1.val < list2.val) ? list1 : list2;
        ListNode nonhead = (list1.val < list2.val) ? list2 : list1;
        head.next = mergeTwoLists(head.next, nonhead);
        return head;
    }
}

我们还可以三行搞定,简直丧心病狂有木有!

C++ 解法四:

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        if (!list1 || (list2 && list1->val > list2->val)) swap(list1, list2);
        if (list1) list1->next = mergeTwoLists(list1->next, list2);
        return list1;
    }
};

Java 解法四:

public class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        if (list1 == null || (list2 != null && list1.val > list2.val)) {
            ListNode t = list1; list1 = list2; list2 = t;
        }
        if (list1 != null) list1.next = mergeTwoLists(list1.next, list2);
        return list1;
    }
}

Github 同步地址:

#21

类似题目:

Merge Sorted Array

Merge k Sorted Lists

Sort List

Shortest Word Distance II

参考资料:

https://leetcode.com/problems/merge-two-sorted-lists/

https://leetcode.com/problems/merge-two-sorted-lists/discuss/9714/14-line-clean-C%2B%2B-Solution

https://leetcode.com/problems/merge-two-sorted-lists/discuss/9814/3-lines-C%2B%2B-(12ms)-and-C-(4ms)

https://leetcode.com/problems/merge-two-sorted-lists/discuss/9715/Java-1-ms-4-lines-codes-using-recursion

LeetCode All in One 题目讲解汇总(持续更新中...)

(欢迎加入博主的知识星球,博主将及时答疑解惑,并分享刷题经验与总结,快快加入吧~)

知识星球 喜欢请点赞,疼爱请打赏❤️~.~

微信打赏

|

Venmo 打赏


---|---

[LeetCode] 22. Generate Parentheses


请点击下方图片观看讲解视频
Click below image to watch YouTube Video
Video

Given n pairs of parentheses, write a function to generate all combinations of well-formed parentheses.

Example 1:

**Input:** n = 3
**Output:** ["((()))","(()())","(())()","()(())","()()()"]

Example 2:

**Input:** n = 1
**Output:** ["()"]

Constraints:

  • 1 <= n <= 8

在 LeetCode 中有关括号的题共有七道,除了这一道的另外六道是 Score of ParenthesesValid Parenthesis StringRemove Invalid ParenthesesDifferent Ways to Add ParenthesesValid ParenthesesLongest Valid Parentheses。这道题给定一个数字n,让生成共有n个括号的所有正确的形式,对于这种列出所有结果的题首先还是考虑用递归 Recursion 来解,由于字符串只有左括号和右括号两种字符,而且最终结果必定是左括号3个,右括号3个,所以这里定义两个变量 left 和 right 分别表示剩余左右括号的个数,如果在某次递归时,左括号的个数大于右括号的个数,说明此时生成的字符串中右括号的个数大于左括号的个数,即会出现 ')(' 这样的非法串,所以这种情况直接返回,不继续处理。如果 left 和 right 都为0,则说明此时生成的字符串已有3个左括号和3个右括号,且字符串合法,则存入结果中后返回。如果以上两种情况都不满足,若此时 left 大于0,则调用递归函数,注意参数的更新,若 right 大于0,则调用递归函数,同样要更新参数,参见代码如下:

C++ 解法一:

class Solution {
public:
    vector<string> generateParenthesis(int n) {
        vector<string> res;
        dfs(n, n, "", res);
        return res;
    }
    void dfs(int left, int right, string cur, vector<string> &res) {
        if (left > right) return;
        if (left == 0 && right == 0) res.push_back(cur);
        else {
            if (left > 0) dfs(left - 1, right, cur + '(', res);
            if (right > 0) dfs(left, right - 1, cur + ')', res);
        }
    }
};

Java 解法一:

public class Solution {
    public List<String> generateParenthesis(int n) {
        List<String> res = new ArrayList<String>();
        helper(n, n, "", res);
        return res;
    }
    void helper(int left, int right, String out, List<String> res) {
        if (left < 0 || right < 0 || left > right) return;
        if (left == 0 && right == 0) {
            res.add(out);
            return;
        }
        helper(left - 1, right, out + "(", res);
        helper(left, right - 1, out + ")", res);
    }
}

再来看那一种方法,这种方法是 CareerCup 书上给的方法,感觉也是满巧妙的一种方法,这种方法的**是找左括号,每找到一个左括号,就在其后面加一个完整的括号,最后再在开头加一个 (),就形成了所有的情况,需要注意的是,有时候会出现重复的情况,所以用 HashSet 数据结构,好处是如果遇到重复项,不会加入到结果中,最后我们再把 HashSet 转为 vector 即可,参见代码如下::

n=1: ()

n=2: (()) ()()

n=3: (()()) ((())) ()(()) (())() ()()()

C++ 解法二:

class Solution {
public:
    vector<string> generateParenthesis(int n) {
        unordered_set<string> st;
        if (n == 0) st.insert("");
        else {
            vector<string> pre = generateParenthesis(n - 1);
            for (auto a : pre) {
                for (int i = 0; i < a.size(); ++i) {
                    if (a[i] == '(') {
                        a.insert(a.begin() + i + 1, '(');
                        a.insert(a.begin() + i + 2, ')');
                        st.insert(a);
                        a.erase(a.begin() + i + 1, a.begin() + i + 3);
                    }
                }
                st.insert("()" + a);
            }
        }
        return vector<string>(st.begin(), st.end());
    }
};

Java 解法二:

public class Solution {
    public List<String> generateParenthesis(int n) {
        Set<String> res = new HashSet<String>();
        if (n == 0) {
            res.add("");
        } else {
            List<String> pre = generateParenthesis(n - 1);
            for (String str : pre) {
                for (int i = 0; i < str.length(); ++i) {
                    if (str.charAt(i) == '(') {
                        str = str.substring(0, i + 1) + "()" + str.substring(i + 1, str.length());
                        res.add(str);
                        str = str.substring(0, i + 1) +  str.substring(i + 3, str.length());
                    }
                }
                res.add("()" + str);
            }
        }
        return new ArrayList(res);
    }
}

Github 同步地址:

#22

类似题目:

Remove Invalid Parentheses

Different Ways to Add Parentheses

Longest Valid Parentheses

Valid Parentheses

Score of Parentheses

Valid Parenthesis String

Letter Combinations of a Phone Number

Check if a Parentheses String Can Be Valid

参考资料:

https://leetcode.com/problems/generate-parentheses/

https://leetcode.com/problems/generate-parentheses/discuss/10127/An-iterative-method.

https://leetcode.com/problems/generate-parentheses/discuss/10337/My-accepted-JAVA-solution

https://leetcode.com/problems/generate-parentheses/discuss/10105/Concise-recursive-C%2B%2B-solution

LeetCode All in One 题目讲解汇总(持续更新中...)

(欢迎加入博主的知识星球,博主将及时答疑解惑,并分享刷题经验与总结,快快加入吧~)

知识星球 喜欢请点赞,疼爱请打赏❤️~.~

微信打赏

|

Venmo 打赏


---|---

[LeetCode] 48. Rotate Image


请点击下方图片观看讲解视频
Click below image to watch YouTube Video
Video

You are given an n x n 2D matrix representing an image, rotate the image by 90 degrees (clockwise).

You have to rotate the image in-place, which means you have to modify the input 2D matrix directly. DO NOT allocate another 2D matrix and do the rotation.

Example 1:

Input: matrix = [[1,2,3],[4,5,6],[7,8,9]]
Output: [[7,4,1],[8,5,2],[9,6,3]]

Example 2:

Input: matrix = [[5,1,9,11],[2,4,8,10],[13,3,6,7],[15,14,12,16]]
Output: [[15,13,2,5],[14,3,4,1],[12,6,8,9],[16,7,10,11]]

Constraints:

  • n == matrix.length == matrix[i].length
  • 1 <= n <= 20
  • -1000 <= matrix[i][j] <= 1000

在计算机图像处理里,旋转图片是很常见的,由于图片的本质是二维数组,所以也就变成了对数组的操作处理,翻转的本质就是某个位置上数移动到另一个位置上,比如用一个简单的例子来分析:

1  2  3       7  4  1 


4  5  6  -->   8  5  2  




7  8  9       9  6  3

对于90度的翻转有很多方法,一步或多步都可以解,先来看一种直接的方法,这种方法是按顺时针的顺序去覆盖前面的数字,从四个顶角开始,然后往中间去遍历,每次覆盖的坐标都是同理,如下:

(i, j) <- (n-1-j, i) <- (n-1-i, n-1-j) <- (j, n-1-i)

这其实是个循环的过程,第一个位置又覆盖了第四个位置,这里i的取值范围是 [0, n/2),j的取值范围是 [i, n-1-i),至于为什么i和j是这个取值范围,为啥i不用遍历 [n/2, n),若仔细观察这些位置之间的联系,不难发现,实际上j列的范围 [i, n-1-i) 顺时针翻转 90 度,正好就是i行的 [n/2, n) 的位置,这个方法每次循环换四个数字,如下所示:

1  2  3               7  2  1            7  4  1


4  5  6      -->      4  5  6   -->    8  5  2  




7  8  9               9  8  3         9  6  3

解法一:

class Solution {
public:
    void rotate(vector<vector<int>>& matrix) {
        int n = matrix.size();
        for (int i = 0; i < n / 2; ++i) {
            for (int j = i; j < n - 1 - i; ++j) {
                int tmp = matrix[i][j];
                matrix[i][j] = matrix[n - 1 - j][i];
                matrix[n - 1 - j][i] = matrix[n - 1 - i][n - 1 - j];
                matrix[n - 1 - i][n - 1 - j] = matrix[j][n - 1 - i];
                matrix[j][n - 1 - i] = tmp;
            }
        }
    }
};

还有一种解法,首先以次对角线为轴翻转,然后再以x轴中线上下翻转即可得到结果,如下图所示(其中蓝色数字表示翻转轴):

1  2  3       9  6  3          7  4  1


4  5  6  -->   8  5  2   -->     8  5  2  




7  8  9       7  4  1          9  6  3

解法二:

class Solution {
public:
    void rotate(vector<vector<int>>& matrix) {
        int n = matrix.size();
        for (int i = 0; i < n - 1; ++i) {
            for (int j = 0; j < n - i; ++j) {
                swap(matrix[i][j], matrix[n - 1- j][n - 1 - i]);
            }
        }
        reverse(matrix.begin(), matrix.end());
    }
};

最后再来看一种方法,这种方法首先对原数组取其转置矩阵,所谓转置矩阵就是以主对角线为轴翻转,然后把每行的数字翻转可得到结果,如下所示(其中蓝色数字表示翻转轴,Github 上可能无法显示颜色,请参见博客园上的帖子):

1  2  3       1  4  7          7  4  1


4  5  6  -->   2  5  8   -->     8  5  2  




7  8  9       3  6  9           9  6  3

解法三:

class Solution {
public:
    void rotate(vector<vector<int>>& matrix) {
        int n = matrix.size();
        for (int i = 0; i < n; ++i) {
            for (int j = i + 1; j < n; ++j) {
                swap(matrix[i][j], matrix[j][i]);
            }
            reverse(matrix[i].begin(), matrix[i].end());
        }
    }
};

Github 同步地址:

#48

类似题目:

Determine Whether Matrix Can Be Obtained By Rotation

参考资料:

https://leetcode.com/problems/rotate-image/

https://leetcode.com/problems/rotate-image/discuss/18895/Clear-Java-solution

https://leetcode.com/problems/rotate-image/discuss/18872/A-common-method-to-rotate-the-image

LeetCode All in One 题目讲解汇总(持续更新中...)

(欢迎加入博主的知识星球,博主将及时答疑解惑,并分享刷题经验与总结,快快加入吧~)

知识星球 喜欢请点赞,疼爱请打赏❤️~.~

微信打赏

|

Venmo 打赏


---|---

[LeetCode] 43. Multiply Strings


请点击下方图片观看讲解视频
Click below image to watch YouTube Video
Video

Given two non-negative integers num1 and num2 represented as strings, return the product of num1 and num2, also represented as a string.

Note: You must not use any built-in BigInteger library or convert the inputs to integer directly.

Example 1:

Input: num1 = "2", num2 = "3"
Output: "6"

Example 2:

Input: num1 = "123", num2 = "456"
Output: "56088"

Constraints:

  • 1 <= num1.length, num2.length <= 200
  • num1 and num2 consist of digits only.
  • Both num1 and num2 do not contain any leading zero, except the number 0 itself.

这道题让我们求两个字符串数字的相乘,输入的两个数和返回的数都是以字符串格式储存的,这样做的原因可能是这样可以计算超大数相乘,可以不受 int 或 long 的数值范围的约束,那么该如何来计算乘法呢,小时候都学过多位数的乘法过程,都是每位相乘然后错位相加,那么这里就是用到这种方法,举个例子,比如 89 x 76,那么根据小学的算术知识,不难写出计算过程如下:

    8 9  <- num2
    7 6  <- num1
-------
    5 4
  4 8
  6 3
5 6
-------
6 7 6 4

如果自己再写些例子出来,不难发现,两数相乘得到的乘积的长度其实其实不会超过两个数字的长度之和,若 num1 长度为m,num2 长度为n,则 num1 x num2 的长度不会超过 m+n,还有就是要明白乘的时候为什么要错位,比如6乘8得到的 48 为啥要跟6乘9得到的 54 错位相加,因为8是十位上的数字,其本身相当于80,所以错开的一位实际上末尾需要补的0。还有一点需要观察出来的就是,num1 和 num2 中任意位置的两个数字相乘,得到的两位数在最终结果中的位置是确定的,比如 num1 中位置为i的数字乘以 num2 中位置为j的数字,那么得到的两位数字的位置为 i+j 和 i+j+1,明白了这些后,就可以进行错位相加了,累加出最终的结果。

由于要从个位上开始相乘,所以从 num1 和 num2 字符串的尾部开始往前遍历,分别提取出对应位置上的字符,将其转为整型后相乘。然后确定相乘后的两位数所在的位置 p1 和 p2,由于 p2 相较于 p1 是低位,所以将得到的两位数 mul 先加到 p2 位置上去,这样可能会导致 p2 位上的数字大于9,所以将十位上的数字要加到高位 p1 上去,只将余数留在 p2 位置,这样每个位上的数字都变成一位。然后要做的是从高位开始,将数字存入结果 res 中,记住 leading zeros 要跳过,最后处理下 corner case,即若结果 res 为空,则返回 "0",否则返回结果 res,代码如下:

class Solution {
public:
    string multiply(string num1, string num2) {
        string res;
        int m = num1.size(), n = num2.size();
        vector<int> vals(m + n);
        for (int i = m - 1; i >= 0; --i) {
            for (int j = n - 1; j >= 0; --j) {
                int mul = (num1[i] - '0') * (num2[j] - '0');
                int p1 = i + j, p2 = i + j + 1, sum = mul + vals[p2];
                vals[p1] += sum / 10;
                vals[p2] = sum % 10;
            }
        }
        for (int val : vals) {
            if (!res.empty() || val != 0) res.push_back(val + '0');
        }
        return res.empty() ? "0" : res;
    }
};

Github 同步地址:

#43

类似题目:

Add Two Numbers

Plus One

Add Binary

Add Strings

Apply Discount to Prices

参考资料:

https://leetcode.com/problems/multiply-strings/

https://leetcode.com/problems/multiply-strings/discuss/17605/Easiest-JAVA-Solution-with-Graph-Explanation

LeetCode All in One 题目讲解汇总(持续更新中...)

(欢迎加入博主的知识星球,博主将及时答疑解惑,并分享刷题经验与总结,快快加入吧~)

知识星球 喜欢请点赞,疼爱请打赏❤️~.~

微信打赏

|

Venmo 打赏


---|---

[LeetCode] 50. Pow(x, n)


请点击下方图片观看讲解视频
Click below image to watch YouTube Video
Video

Implement pow(x, n), which calculates x raised to the power n (i.e., x^n).

Example 1:

Input: x = 2.00000, n = 10
Output: 1024.00000

Example 2:

Input: x = 2.10000, n = 3
Output: 9.26100

Example 3:

Input: x = 2.00000, n = -2
Output: 0.25000
Explanation: 2-2 = 1/22 = 1/4 = 0.25 

Constraints:

  • -100.0 < x < 100.0
  • -2^31 <= n <= 2^31-1
  • n is an integer.
  • Either x is not zero or n > 0.
  • -10^4 <= x^n <= 10^4

这道题让我们求x的n次方,如果只是简单的用个 for 循环让x乘以自己n次的话,未免也把 LeetCode 上的题想的太简单了,一句话形容图样图森破啊。OJ 因超时无法通过,所以需要优化,使其在更有效的算出结果来们可以用递归来折半计算,每次把n缩小一半,这样n最终会缩小到0,任何数的0次方都为1,这时候再往回乘,如果此时n是偶数,直接把上次递归得到的值算个平方返回即可,如果是奇数,则还需要乘上个x的值。还有一点需要引起注意的是n有可能为负数,对于n是负数的情况,我可以先用其绝对值计算出一个结果再取其倒数即可,之前是可以的,但是现在 test case 中加了个负2的31次方后,这就不行了,因为其绝对值超过了整型最大值,会有溢出错误,不过可以用另一种写法只用一个函数,在每次递归中处理n的正负,然后做相应的变换即可,代码如下:

解法一:

class Solution {
public:
    double myPow(double x, int n) {
        if (n == 0) return 1;
        double half = myPow(x, n / 2);
        if (n % 2 == 0) return half * half;
        return n > 0 ? half * half * x : half * half / x;
    }
};

这道题还有迭代的解法,让i初始化为n,然后看i是否是2的倍数,不是的话就让 res 乘以x。然后x乘以自己,i每次循环缩小一半,直到为0停止循环。最后看n的正负,如果为负,返回其倒数,参见代码如下:

解法二:

class Solution {
public:
    double myPow(double x, int n) {
        double res = 1.0;
        for (int i = n; i != 0; i /= 2) {
            if (i % 2 != 0) res *= x;
            x *= x;
        }
        return n < 0 ? 1 / res : res;
    }
};

Github 同步地址:

#50

类似题目:

Sqrt(x)

Super Pow

Count Collisions of Monkeys on a Polygon

参考资料:

https://leetcode.com/problems/powx-n/

https://leetcode.com/problems/powx-n/discuss/19733/simple-iterative-lg-n-solution

https://leetcode.com/problems/powx-n/discuss/19546/Short-and-easy-to-understand-solution

https://leetcode.com/problems/powx-n/discuss/19544/5-different-choices-when-talk-with-interviewers

LeetCode All in One 题目讲解汇总(持续更新中...)

(欢迎加入博主的知识星球,博主将及时答疑解惑,并分享刷题经验与总结,快快加入吧~)

知识星球 喜欢请点赞,疼爱请打赏❤️~.~

微信打赏

|

Venmo 打赏


---|---

[LeetCode] 35. Search Insert Position


请点击下方图片观看讲解视频
Click below image to watch YouTube Video
Video

Given a sorted array of distinct integers and a target value, return the index if the target is found. If not, return the index where it would be if it were inserted in order.

You must write an algorithm with O(log n) runtime complexity.

Example 1:

**Input:** nums = [1,3,5,6], target = 5
**Output:** 2

Example 2:

**Input:** nums = [1,3,5,6], target = 2
**Output:** 1

Example 3:

**Input:** nums = [1,3,5,6], target = 7
**Output:** 4

Constraints:

  • 1 <= nums.length <= 10^4
  • -10^4 <= nums[i] <= 10^4
  • nums contains distinct values sorted in ascending order.
  • -10^4 <= target <= 10^4

这道题基本没有什么难度,实在不理解为啥还是 Medium 难度的,完完全全的应该是 Easy 啊(貌似现在已经改为 Easy 类了),三行代码搞定的题,只需要遍历一遍原数组,若当前数字大于或等于目标值,则返回当前坐标,如果遍历结束了,说明目标值比数组中任何一个数都要大,则返回数组长度n即可,代码如下:

解法一:

class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        for (int i = 0; i < nums.size(); ++i) {
            if (nums[i] >= target) return i;
        }
        return nums.size();
    }
};

当然,我们还可以用二分搜索法来优化时间复杂度,而且个人认为这种方法应该是面试官们想要考察的算法吧,属于博主之前的总结帖 LeetCode Binary Search Summary 二分搜索法小结 中第二类 - 查找不小于目标值的数,参见代码如下:

解法二:

class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        if (nums.back() < target) return nums.size();
        int left = 0, right = nums.size();
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] < target) left = mid + 1;
            else right = mid;
        }
        return right;
    }
};

Github 同步地址:

#35

类似题目:

First Bad Version

参考资料:

https://leetcode.com/problems/search-insert-position/

https://leetcode.com/problems/search-insert-position/discuss/15372/Simple-Java-solution

https://leetcode.com/problems/search-insert-position/discuss/15080/My-8-line-Java-solution

LeetCode All in One 题目讲解汇总(持续更新中...)

(欢迎加入博主的知识星球,博主将及时答疑解惑,并分享刷题经验与总结,快快加入吧~)

知识星球 喜欢请点赞,疼爱请打赏❤️~.~

微信打赏

|

Venmo 打赏


---|---

[LeetCode] 4. Median of Two Sorted Arrays


请点击下方图片观看讲解视频
Click below image to watch YouTube Video
Video

Given two sorted arrays nums1 and nums2 of size m and n respectively, return the median of the two sorted arrays.

The overall run time complexity should be O(log (m+n)).

Example 1:

**Input:** nums1 = [1,3], nums2 = [2]
**Output:** 2.00000
**Explanation:** merged array = [1,2,3] and median is 2.

Example 2:

**Input:** nums1 = [1,2], nums2 = [3,4]
**Output:** 2.50000
**Explanation:** merged array = [1,2,3,4] and median is (2 + 3) / 2 = 2.5.

Constraints:

  • nums1.length == m
  • nums2.length == n
  • 0 <= m <= 1000
  • 0 <= n <= 1000
  • 1 <= m + n <= 2000
  • -106 <= nums1[i], nums2[i] <= 106

image

这道题让我们求两个有序数组的中位数,而且限制了时间复杂度为 O(log (m+n)),看到这个时间复杂度,自然而然的想到了应该使用二分查找法来求解。但是这道题被定义为 Hard 也是有其原因的,难就难在要在两个未合并的有序数组之间使用二分法,如果这道题只有一个有序数组,让求中位数的话,估计就是个 Easy 题。对于这道题来说,可以将两个有序数组混合起来成为一个有序数组再做吗,图样图森破,这个时间复杂度限制的就是告诉你金坷垃别想啦。还是要用二分法,而且是在两个数组之间使用,感觉很高端啊。回顾一下中位数的定义,如果某个有序数组长度是奇数,那么其中位数就是最中间那个,如果是偶数,那么就是最中间两个数字的平均值。这里对于两个有序数组也是一样的,假设两个有序数组的长度分别为m和n,由于两个数组长度之和 m+n 的奇偶不确定,因此需要分情况来讨论,对于奇数的情况,直接找到最中间的数即可,偶数的话需要求最中间两个数的平均值。为了简化代码,不分情况讨论,使用一个小 trick,分别找第 (m+n+1) / 2 个,和 (m+n+2) / 2 个,然后求其平均值即可,这对奇偶数均适用。若 m+n 为奇数的话,那么其实 (m+n+1) / 2 和 (m+n+2) / 2 的值相等,相当于两个相同的数字相加再除以2,还是其本身。

好,这里需要定义一个函数来在两个有序数组中找到第K个元素,下面重点来看如何实现找到第K个元素。首先,为了避免拷贝产生新的数组从而增加时间复杂度,使用两个变量i和j分别来标记数组 nums1 和 nums2 的起始位置。然后来处理一些 corner cases,比如当某一个数组的起始位置大于等于其数组长度时,说明其所有数字均已经被淘汰了,相当于一个空数组了,那么实际上就变成了在另一个数组中找数字,直接就可以找出来了。还有就是如果 K=1 的话,只要比较 nums1 和 nums2 的起始位置i和j上的数字就可以了。难点就在于一般的情况怎么处理?因为需要在两个有序数组中找到第K个元素,为了加快搜索的速度,可以使用二分法,那么对谁二分呢,数组么?其实要对K二分,意思是需要分别在 nums1 和 nums2 中查找第 K/2 个元素,注意这里由于两个数组的长度不定,所以有可能某个数组没有第 K/2 个数字,所以需要先 check 一下,数组中到底存不存在第 K/2 个数字,如果存在就取出来,否则就赋值上一个整型最大值(目的是要在 nums1 或者 nums2 中先淘汰 K/2 个较小的数字,判断的依据就是看 midVal1 和 midVal2 谁更小,但如果某个数组的个数都不到 K/2 个,自然无法淘汰,所以将其对应的 midVal 值设为整型最大值,以保证其不会被淘汰),若某个数组没有第 K/2 个数字,则淘汰另一个数组的前 K/2 个数字即可。举个例子来说吧,比如 nums1 = {3},nums2 = {2, 4, 5, 6, 7},K=4,要找两个数组混合中第4个数字,则分别在 nums1 和 nums2 中找第2个数字,而 nums1 中只有一个数字,不存在第二个数字,则 nums2 中的前2个数字可以直接跳过,为啥呢,因为要求的是整个混合数组的第4个数字,不管 nums1 中的那个数字是大是小,第4个数字绝不会出现在 nums2 的前两个数字中,所以可以直接跳过。

有没有可能两个数组都不存在第 K/2 个数字呢,这道题里是不可能的,因为K不是任意给的,而是给的 m+n 的中间值,所以必定至少会有一个数组是存在第 K/2 个数字的。最后就是二分法的核心啦,比较这两个数组的第 K/2 小的数字 midVal1 和 midVal2 的大小,如果第一个数组的第 K/2 个数字小的话,那么说明要找的数字肯定不在 nums1 中的前 K/2 个数字,可以将其淘汰,将 nums1 的起始位置向后移动 K/2 个,并且此时的K也自减去 K/2,调用递归,举个例子来说吧,比如 nums1 = {1, 3},nums2 = {2, 4, 5},K=3,要找两个数组混合中第3个数字,那么分别在 nums1 和 nums2 中找第 K/2 个数字,即第1个数字,nums1 中的第1个数字是1,nums2 中的第1个数字是2,由于1小于2,所以混合数组中第3个数字肯定不在 nums1 中的前 K/2 个数字中,可以将 nums1 的起始位置向后移动 K/2 个。反之,淘汰 nums2 中的前 K/2 个数字,并将 nums2 的起始位置向后移动 K/2 个,并且此时的K也自减去 K/2,调用递归即可,参见代码如下:

C++ 解法一:

class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        int m = nums1.size(), n = nums2.size(), left = (m + n + 1) / 2, right = (m + n + 2) / 2;
        return (findKth(nums1, 0, nums2, 0, left) + findKth(nums1, 0, nums2, 0, right)) / 2.0;
    }
    int findKth(vector<int>& nums1, int i, vector<int>& nums2, int j, int k) {
        if (i >= nums1.size()) return nums2[j + k - 1];
        if (j >= nums2.size()) return nums1[i + k - 1];
        if (k == 1) return min(nums1[i], nums2[j]);
        int midVal1 = (i + k / 2 - 1 < nums1.size()) ? nums1[i + k / 2 - 1] : INT_MAX;
        int midVal2 = (j + k / 2 - 1 < nums2.size()) ? nums2[j + k / 2 - 1] : INT_MAX;
        if (midVal1 < midVal2) {
            return findKth(nums1, i + k / 2, nums2, j, k - k / 2);
        } else {
            return findKth(nums1, i, nums2, j + k / 2, k - k / 2);
        }
    }
};

Java 解法一:

public class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int m = nums1.length, n = nums2.length, left = (m + n + 1) / 2, right = (m + n + 2) / 2;
        return (findKth(nums1, 0, nums2, 0, left) + findKth(nums1, 0, nums2, 0, right)) / 2.0;
    }
    int findKth(int[] nums1, int i, int[] nums2, int j, int k) {
        if (i >= nums1.length) return nums2[j + k - 1];
        if (j >= nums2.length) return nums1[i + k - 1];
        if (k == 1) return Math.min(nums1[i], nums2[j]);
        int midVal1 = (i + k / 2 - 1 < nums1.length) ? nums1[i + k / 2 - 1] : Integer.MAX_VALUE;
        int midVal2 = (j + k / 2 - 1 < nums2.length) ? nums2[j + k / 2 - 1] : Integer.MAX_VALUE;
        if (midVal1 < midVal2) {
            return findKth(nums1, i + k / 2, nums2, j, k - k / 2);
        } else {
            return findKth(nums1, i, nums2, j + k / 2, k - k / 2);
        }
    }
}

上面的解法一直使用的是原数组,同时用了两个变量来分别标记当前的起始位置。我们也可以直接生成新的数组,这样就不要用起始位置变量了,不过拷贝数组的操作可能会增加时间复杂度,也许会超出限制,不过就算当个思路拓展也是极好的。首先要判断数组是否为空,为空的话,直接在另一个数组找第K个即可。还有一种情况是当 K = 1 时,表示要找第一个元素,只要比较两个数组的第一个元素,返回较小的那个即可。这里分别取出两个数组的第 K/2 个数字的位置坐标i和j,为了避免数组没有第 K/2 个数组的情况,每次都和数组长度做比较,取出较小值。这里跟上面的解法有些许不同,上面解法直接取出的是值,而这里取出的是位置坐标,但是**都是很类似的。不同在于,上面解法中每次固定淘汰 K/2 个数字,而这里由于取出了合法的i和j,所以每次淘汰i或j个。评论区有网友提出,可以让 j = k-i,这样也是对的,可能还更好一些,收敛速度可能会更快一些,参见代码如下:

C++ 解法二:

class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        int m = nums1.size(), n = nums2.size();
        return (findKth(nums1, nums2, (m + n + 1) / 2) + findKth(nums1, nums2, (m + n + 2) / 2)) / 2.0;
    }
    int findKth(vector<int> nums1, vector<int> nums2, int k) {
        if (nums1.empty()) return nums2[k - 1];
        if (nums2.empty()) return nums1[k - 1];
        if (k == 1) return min(nums1[0], nums2[0]);
        int i = min((int)nums1.size(), k / 2), j = min((int)nums2.size(), k / 2);
        if (nums1[i - 1] > nums2[j - 1]) {
            return findKth(nums1, vector<int>(nums2.begin() + j, nums2.end()), k - j);
        } else {
            return findKth(vector<int>(nums1.begin() + i, nums1.end()), nums2, k - i);
        }
        return 0;
    }
};

Java 解法二:

public class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int m = nums1.length, n = nums2.length, left = (m + n + 1) / 2, right = (m + n + 2) / 2;
        return (findKth(nums1, nums2, left) + findKth(nums1, nums2, right)) / 2.0;
    }
    int findKth(int[] nums1, int[] nums2, int k) {
        int m = nums1.length, n = nums2.length;
        if (m == 0) return nums2[k - 1];
        if (n == 0) return nums1[k - 1];
        if (k == 1) return Math.min(nums1[0], nums2[0]);
        int i = Math.min(m, k / 2), j = Math.min(n, k / 2);
        if (nums1[i - 1] > nums2[j - 1]) {
            return findKth(nums1, Arrays.copyOfRange(nums2, j, n), k - j);
        } else {
            return findKth(Arrays.copyOfRange(nums1, i, m), nums2, k - i);
        }
    }
}

此题还能用迭代形式的二分搜索法来解,是一种相当巧妙的应用,这里就参照 stellari 大神的帖子 来讲解吧。所谓的中位数,换一种角度去看,其实就是把一个有序数组分为长度相等的两段,中位数就是前半段的最大值和后半段的最小值的平均数,也就是离分割点相邻的两个数字的平均值。比如说对于偶数个数组 [1 3 5 7],那么分割开来就是 [1 3 / 5 7],其中 '/' 表示分割点,中位数就是3和5的平均值。对于奇数个数组 [1 3 4 5 7],可以分割为 [1 3 4 / 4 5 7],可以发现左右两边都有个4,则中位数是两个4的平均数,还是4。这里使用L表示分割点左边的数字,R表示分割点右边的数字,则对于 [1 3 5 7] 来说,L=3,R=5。对于 [1 3 4 5 7] 来说,L=4,R=4。那么对于长度为N的数组来说,可以分别得到L和R的位置,如下所示:

N        Index of L        Index of R
1            0                0
2            0                1
3            1                1
4            1                2
5            2                2
6            2                3
7            3                3
8            3                4

观察上表,可以得到规律,Idx(L)= (N-1)/2,idx(R) = N/2,所以中位数可以用下式表示:

(L + R) / 2 = (A[(N - 1) / 2] + A[N / 2]) / 2

为了统一数组长度为奇数和偶数的情况,可以使用一个小 tricky,即在每个数字的两边都加上一个特殊字符,比如井号,这个 tricky 其实在马拉车算法中也使用过,可以参见博主之前的帖子 Manacher's Algorithm 马拉车算法。这样做的好处是不管奇数或者偶数,加井号后数组的长度都是奇数,并且切割点的位置也是确定的,比如:

[1 3 5 7]    ->    [# 1 # 3 # 5 # 7 #]        N = 4
index               0 1 2 3 4 5 6 7 8         newN = 9


[1 3 4 5 7]    ->    [# 1 # 3 # 4 # 5 # 7 #]        N = 5  

index                 0 1 2 3 4 5 6 7 8 9 10        newN = 11

这里的N是原数组的长度,newN 是添加井号后新数组的长度,可以发现 newN = 2N+1,而且切割点永远都在新数组中坐标为N的位置,且 idx(L) = (N-1)/2,idx(R) = N/2,这里的N就可以换成分割点的位置,岂不美哉(注意这里的 idx(L) 和 idx(R) 表示的是在未填充#号的坐标位置)!现在假设有两个数组:

[1 3 4 5 7]    ->    [# 1 # 3 # 4 # 5 # 7 #]        N1 = 5
index                 0 1 2 3 4 5 6 7 8 9 10        newN1 = 11


[1 2 2 2]    ->    [# 1 # 2 # 2 # 2 #]        N2 = 4  

index               0 1 2 3 4 5 6 7 8         newN2 = 9

跟只有一个数组的情况类似,这里需要找到一个切割点,使得其分别可以将两个数组分成左右两部分,需要满足的是两个左半边中的任意一个数字都要小于两个右半边数组的数字,注意这里可能有的左半边或右半边会为空,但是两个左半边数字的个数和应该等于两个右半边的个数和。这里还可以观察出一些规律:

1. 总共有 2N1 + 2N2 + 2 个位置,那么除去两个分割点,两个左右半边应该各有 N1 + N2 个数字。

2. 因此,对于一个在 A2 数组中的分割点位置 C2 = K,在 A1 数组中的位置应该为 C1 = N1 + N2 - K,比如假如在 A2 中的分割点位置为 C2 = 2,那么在 A1 中的位置为 C1 = 4 + 5 - C2 = 7。

[# 1 # 3 # 4 # (5/5) # 7 #]


[# 1 / 2 # 2 # 2 #]

3. 假如两个数组都被分割了,那么就应该会有两个L和R,分别是:

L1 = A1[(C1 - 1) / 2]
R1 = A1[C1 / 2]


L2 = A2[(C2 - 1) / 2]  

R2 = A2[C2 / 2]

对于上面的例子就有:

L1 = A1[(7 - 1) / 2] = A1[3] = 5
R1 = A1[7 / 2] = A1[3] = 5


L2 = A2[(2 - 1) / 2] = A2[0] = 1  

R2 = A2[2 / 2] = A2[1] = 2

现在需要检测这个切割点是否是正确的中位数的切割点,那么根据之前的分析,任意的左半边的数字都需要小于等于右半边的数字,L1 和 L2 是左半边的最大的数字,R1 和 R2 是右半边的最小的数字,所以需要满足下列关系:

L1 <= R1 && L1 <= R2 && L2 <= R1 && L2 <= R2

由于两个数组都是有序的,所以 L1 <= R1 和 L2 <= R2 都是满足的,那么就只需要满足下列的不等式即可:

L1 <= R2 && L2 <= R1

这样的话就可以利用二分搜索了,假如 L1 > R2 的话,说明数组 A1 的左半边的数字过大了,需要把切割点 C1 往左移动。假如 L2 > R1,说明数组 A2 的左半边数字过大,需要把分割点 C2 左移。若满足上面的条件,说明当前切割点就是正确的,那么中位数就可以求出来了,即为:

(max(L1, L2) + min(R1, R2)) / 2

最后还有两点注意事项:

1. 由于 C1 和 C2 是可以互相计算而得,即一个确定了,另一个就可以计算出来了。所以尽量去移动较短的那个数组,这样得到的时间复杂度为 O(lg(min(N1, N2)))。

2. 对于 corner case 的处理,当切割点在 0 或者 2n 的位置时,将L或R的值分别赋值为整型最小值和最大值,这不会改变正确的切割点的位置,会使得代码实现更加方便。

C++ 解法三:

class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        int m = nums1.size(), n = nums2.size();
        if (m < n) return findMedianSortedArrays(nums2, nums1);
        if (n == 0) return ((double)nums1[(m - 1) / 2] + (double)nums1[m / 2]) / 2.0;
        int left = 0, right = n * 2;
        while (left <= right) {
            int mid2 = (left + right) / 2;
            int mid1 = m + n - mid2;
            double L1 = mid1 == 0 ? INT_MIN : nums1[(mid1 - 1) / 2];
            double L2 = mid2 == 0 ? INT_MIN : nums2[(mid2 - 1) / 2];
            double R1 = mid1 == m * 2 ? INT_MAX : nums1[mid1 / 2];
            double R2 = mid2 == n * 2 ? INT_MAX : nums2[mid2 / 2];
            if (L1 > R2) left = mid2 + 1;
            else if (L2 > R1) right = mid2 - 1;
            else return (max(L1, L2) + min(R1, R2)) / 2;
        }
        return -1;
    }
};

Java 解法三:

public class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int m = nums1.length, n = nums2.length;
        if (m < n) return findMedianSortedArrays(nums2, nums1);
        if (n == 0) return (nums1[(m - 1) / 2] + nums1[m / 2]) / 2.0;
        int left = 0, right = 2 * n;
        while (left <= right) {
            int mid2 = (left + right) / 2;
            int mid1 = m + n - mid2;
            double L1 = mid1 == 0 ? Double.MIN_VALUE : nums1[(mid1 - 1) / 2];
            double L2 = mid2 == 0 ? Double.MIN_VALUE : nums2[(mid2 - 1) / 2];
            double R1 = mid1 == m * 2 ? Double.MAX_VALUE : nums1[mid1 / 2];
            double R2 = mid2 == n * 2 ? Double.MAX_VALUE : nums2[mid2 / 2];
            if (L1 > R2) left = mid2 + 1;
            else if (L2 > R1) right = mid2 - 1;
            else return (Math.max(L1, L2) + Math.min(R1, R2)) / 2;
        }
        return -1;
    }
}

Github 同步地址:

#4

参考资料:

https://leetcode.com/problems/median-of-two-sorted-arrays/

https://leetcode.com/problems/median-of-two-sorted-arrays/discuss/2496/Concise-JAVA-solution-based-on-Binary-Search

https://leetcode.com/problems/median-of-two-sorted-arrays/discuss/2499/Share-my-simple-O(log(m%2Bn))-solution-for-your-reference

https://leetcode.com/problems/median-of-two-sorted-arrays/discuss/2471/Very-concise-O(log(min(MN)))-iterative-solution-with-detailed-explanation

LeetCode All in One 题目讲解汇总(持续更新中...)

(欢迎加入博主的知识星球,博主将及时答疑解惑,并分享刷题经验与总结,快快加入吧~)

知识星球 喜欢请点赞,疼爱请打赏❤️~.~

微信打赏

|

Venmo 打赏


---|---

[LeetCode] 1. Two Sum


请点击下方图片观看讲解视频
Click below image to watch YouTube Video
Video

Given an array of integers nums and an integer target, return indices of the two numbers such that they add up totarget.

You may assume that each input would have exactly one solution, and you may not use the same element twice.

You can return the answer in any order.

Example 1:

**Input:** nums = [2,7,11,15], target = 9
**Output:** [0,1]
**Explanation:** Because nums[0] + nums[1] == 9, we return [0, 1].

Example 2:

**Input:** nums = [3,2,4], target = 6
**Output:** [1,2]

Example 3:

**Input:** nums = [3,3], target = 6
**Output:** [0,1]

Constraints:

  • 2 <= nums.length <= 104
  • -109 <= nums[i] <= 109
  • -109 <= target <= 109
  • Only one valid answer exists.

Follow-up: Can you come up with an algorithm that is less than O(n2) time complexity?

啦啦啦,欢迎开启 LeetCode 刷题的旅程,这将是一段漫长而又艰辛的旅程,这是一条攀登珠穆朗玛的皑皑雪山路,这是通向 One Piece 宝藏的伟大航路,这是成为火影的无比残酷的修罗场,这是打破吊丝与高富帅之间界限的崩玉。但请不要害怕,在老船长 Grandyang 博主的带领下,必将一路披荆斩棘,将各位带到成功的彼岸,不过一定要牢记的是,不要下船,不要中途放弃,要坚持,要自我修炼,不断成长!那么,起航吧~这道 Two Sum 的题目作为 LeetCode 的开篇之题,乃是经典中的经典,正所谓‘ 平生不识 TwoSum,刷尽 LeetCode 也枉然 ’,就像英语单词书的第一个单词总是 Abandon 一样,很多没有毅力坚持的人就只能记住这一个单词,所以通常情况下单词书就前几页有翻动的痕迹,后面都是崭新如初,道理不需多讲,鸡汤不必多灌,明白的人自然明白。

这道题给了我们一个数组,还有一个目标数target,让找到两个数字,使其和为 target,乍一看就感觉可以用暴力搜索,但是猜到 OJ 肯定不会允许用暴力搜索这么简单的方法,于是去试了一下,果然是 Time Limit Exceeded,这个算法的时间复杂度是 O(n^2)。那么只能想个 O(n) 的算法来实现,由于暴力搜索的方法是遍历所有的两个数字的组合,然后算其和,这样虽然节省了空间,但是时间复杂度高。一般来说,为了提高时间的复杂度,需要用空间来换,这算是一个 trade off 吧,但这里只想用线性的时间复杂度来解决问题,就是说只能遍历一个数字,那么另一个数字呢,可以事先将其存储起来,使用一个 HashMap,来建立数字和其坐标位置之间的映射,由于 HashMap 是常数级的查找效率,这样在遍历数组的时候,用 target 减去遍历到的数字,就是另一个需要的数字了,直接在 HashMap 中查找其是否存在即可,注意要判断查找到的数字不是第一个数字,比如 target 是4,遍历到了一个2,那么另外一个2不能是之前那个2,整个实现步骤为:先遍历一遍数组,建立 HashMap 映射,然后再遍历一遍,开始查找,找到则记录 index。代码如下:

C++ 解法一:

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int, int> m;
        vector<int> res;
        for (int i = 0; i < nums.size(); ++i) {
            m[nums[i]] = i;
        }
        for (int i = 0; i < nums.size(); ++i) {
            int t = target - nums[i];
            if (m.count(t) && m[t] != i) {
                res.push_back(i);
                res.push_back(m[t]);
                break;
            }
        }
        return res;
    }
};

Java 解法一:

public class Solution {
    public int[] twoSum(int[] nums, int target) {
        HashMap<Integer, Integer> m = new HashMap<Integer, Integer>();
        int[] res = new int[2];
        for (int i = 0; i < nums.length; ++i) {
            m.put(nums[i], i);
        }
        for (int i = 0; i < nums.length; ++i) {
            int t = target - nums[i];
            if (m.containsKey(t) && m.get(t) != i) {
                res[0] = i;
                res[1] = m.get(t);
                break;
            }
        }
        return res;
    }
} 

或者可以写的更加简洁一些,把两个 for 循环合并成一个:

C++ 解法二:

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int, int> m;
        for (int i = 0; i < nums.size(); ++i) {
            if (m.count(target - nums[i])) {
                return {i, m[target - nums[i]]};
            }
            m[nums[i]] = i;
        }
        return {};
    }
};

Java 解法二:

public class Solution {
    public int[] twoSum(int[] nums, int target) {
        HashMap<Integer, Integer> m = new HashMap<Integer, Integer>();
        int[] res = new int[2];
        for (int i = 0; i < nums.length; ++i) {
            if (m.containsKey(target - nums[i])) {
                res[0] = i;
                res[1] = m.get(target - nums[i]);
                break;
            }
            m.put(nums[i], i);
        }
        return res;
    }
}

Github 同步地址:

#1

类似题目:

4Sum

3Sum Smaller

3Sum Closest

3Sum

Two Sum III - Data structure design

Two Sum II - Input array is sorted

参考资料:

https://leetcode.com/problems/two-sum/

https://leetcode.com/problems/two-sum/discuss/3/Accepted-Java-O(n)-Solution

https://leetcode.com/problems/two-sum/discuss/13/Accepted-C++-O(n)-Solution

LeetCode All in One 题目讲解汇总(持续更新中...)

(欢迎加入博主的知识星球,博主将及时答疑解惑,并分享刷题经验与总结,快快加入吧~)

知识星球 喜欢请点赞,疼爱请打赏❤️~.~

微信打赏

|

Venmo 打赏


---|---

[LeetCode] 34. Find First and Last Position of Element in Sorted Array


请点击下方图片观看讲解视频
Click below image to watch YouTube Video
Video

Given an array of integers nums sorted in non-decreasing order, find the starting and ending position of a given target value.

If target is not found in the array, return [-1, -1].

You must write an algorithm with O(log n) runtime complexity.

Example 1:

**Input:** nums = [5,7,7,8,8,10], target = 8
**Output:** [3,4]

Example 2:

**Input:** nums = [5,7,7,8,8,10], target = 6
**Output:** [-1,-1]

Example 3:

**Input:** nums = [], target = 0
**Output:** [-1,-1]

Constraints:

  • 0 <= nums.length <= 10^5
  • -10^9 <= nums[i] <= 10^9
  • nums is a non-decreasing array.
  • -10^9 <= target <= 10^9

这道题让我们在一个有序整数数组中寻找相同目标值的起始和结束位置,而且限定了时间复杂度为 O(logn),这是典型的二分查找法的时间复杂度,所以这里也需要用此方法,思路是首先对原数组使用二分查找法,找出其中一个目标值的位置,然后向两边搜索找出起始和结束的位置,代码如下:

解法一:

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        int idx = search(nums, 0, nums.size() - 1, target);
        if (idx == -1) return {-1, -1};
        int left = idx, right = idx;
        while (left > 0 && nums[left - 1] == nums[idx]) --left;
        while (right < nums.size() - 1 && nums[right + 1] == nums[idx]) ++right;
        return {left, right};
    }
    int search(vector<int>& nums, int left, int right, int target) {
        if (left > right) return -1;
        int mid = left + (right - left) / 2;
        if (nums[mid] == target) return mid;
        if (nums[mid] < target) return search(nums, mid + 1, right, target);
        else return search(nums, left, mid - 1, target);
    }
};

可能有些人会觉得上面的算法不是严格意义上的 O(logn) 的算法,因为在最坏的情况下会变成 O(n),比如当数组里的数全是目标值的话,从中间向两边找边界就会一直遍历完整个数组,那么下面来看一种真正意义上的 O(logn) 的算法,使用两次二分查找法,第一次找到左边界,第二次调用找到右边界即可,具体代码如下:

解法二:

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        vector<int> res(2, -1);
        int left = 0, right = nums.size();
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] < target) left = mid + 1;
            else right = mid;
        }
        if (right == nums.size() || nums[right] != target) return res;
        res[0] = right;
        right = nums.size();
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] <= target) left = mid + 1;
            else right = mid;
        }
        res[1] = right - 1;
        return res;
    }
};

其实我们也可以只使用一个二分查找的子函数,来同时查找出第一个和最后一个位置。如何只用查找第一个大于等于目标值的二分函数来查找整个范围呢,这里用到了一个小 trick,首先来查找起始位置的 target,就是在数组中查找第一个大于等于 target 的位置,当返回的位置越界,或者该位置上的值不等于 target 时,表示数组中没有 target,直接返回 {-1, -1} 即可。若查找到了 target 值,则再查找第一个大于等于 target+1 的位置,然后把返回的位置减1,就是 target 的最后一个位置,即便是返回的值越界了,减1后也不会越界,这样就实现了使用一个二分查找函数来解题啦,参见代码如下:

解法三:

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        int start = firstGreaterEqual(nums, target);
        if (start == nums.size() || nums[start] != target) return {-1, -1};
        return {start, firstGreaterEqual(nums, target + 1) - 1};
    }
    int firstGreaterEqual(vector<int>& nums, int target) {
        int left = 0, right = nums.size();
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] < target) left = mid + 1;
            else right = mid;
        }
        return right;
    }
};

讨论:这道题的二分搜索的解法实际上使用了博主之前的总结帖 LeetCode Binary Search Summary 二分搜索法小结 中归纳总结的前三类,是一道非常好的考察二分搜索法的题目,请大家一定要注意区分各种类型之间的不同~

Github 同步地址:

#34

类似题目:

First Bad Version

参考资料:

https://leetcode.com/problems/find-first-and-last-position-of-element-in-sorted-array/

https://leetcode.com/problems/find-first-and-last-position-of-element-in-sorted-array/discuss/14699/Clean-iterative-solution-with-two-binary-searches-(with-explanation)

https://leetcode.com/problems/find-first-and-last-position-of-element-in-sorted-array/discuss/14701/A-very-simple-Java-solution-with-only-one-binary-search-algorithm

LeetCode All in One 题目讲解汇总(持续更新中...)

(欢迎加入博主的知识星球,博主将及时答疑解惑,并分享刷题经验与总结,快快加入吧~)

知识星球 喜欢请点赞,疼爱请打赏❤️~.~

微信打赏

|

Venmo 打赏


---|---

[LeetCode] 19. Remove Nth Node From End of List


请点击下方图片观看讲解视频
Click below image to watch YouTube Video
Video

Given the head of a linked list, remove the nth node from the end of the list and return its head.

Example 1:

**Input:** head = [1,2,3,4,5], n = 2
**Output:** [1,2,3,5]

Example 2:

**Input:** head = [1], n = 1
**Output:** []

Example 3:

**Input:** head = [1,2], n = 1
**Output:** [1]

Constraints:

  • The number of nodes in the list is sz.
  • 1 <= sz <= 30
  • 0 <= Node.val <= 100
  • 1 <= n <= sz

Follow up: Could you do this in one pass?

这道题让我们移除链表倒数第N个节点,限定n一定是有效的,即n不会大于链表中的元素总数。还有题目要求一次遍历解决问题,那么就得想些比较巧妙的方法了。比如首先要考虑的时,如何找到倒数第N个节点,由于只允许一次遍历,所以不能用一次完整的遍历来统计链表中元素的个数,而是遍历到对应位置就应该移除了。那么就需要用两个指针来帮助解题,pre 和 cur 指针。首先 cur 指针先向前走N步,如果此时 cur 指向空,说明N为链表的长度,则需要移除的为首元素,那么此时返回 head->next 即可,如果 cur 存在,再继续往下走,此时 pre 指针也跟着走,直到 cur 为最后一个元素时停止,此时 pre 指向要移除元素的前一个元素,再修改指针跳过需要移除的元素即可,参见代码如下:

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode *pre = head, *cur = head;
        for (int i = 0; i < n; ++i) cur = cur->next;
        if (!cur) return head->next;
        while (cur->next) {
            cur = cur->next;
            pre = pre->next;
        }
        pre->next = pre->next->next;
        return head;
    }
};

Github 同步地址:

#19

类似题目:

Linked List Cycle

Linked List Cycle II

Swapping Nodes in a Linked List

Delete N Nodes After M Nodes of a Linked List

Delete the Middle Node of a Linked List

参考资料:

https://leetcode.com/problems/remove-nth-node-from-end-of-list/

https://leetcode.com/problems/remove-nth-node-from-end-of-list/discuss/8812/My-short-C%2B%2B-solution

https://leetcode.com/problems/remove-nth-node-from-end-of-list/discuss/8804/Simple-Java-solution-in-one-pass

LeetCode All in One 题目讲解汇总(持续更新中...)

(欢迎加入博主的知识星球,博主将及时答疑解惑,并分享刷题经验与总结,快快加入吧~)

知识星球 喜欢请点赞,疼爱请打赏❤️~.~

微信打赏

|

Venmo 打赏


---|---

[LeetCode] 25. Reverse Nodes in k-Group


请点击下方图片观看讲解视频
Click below image to watch YouTube Video
Video

Given the head of a linked list, reverse the nodes of the list k at a time, and return the modified list.

k is a positive integer and is less than or equal to the length of the linked list. If the number of nodes is not a multiple of k then left-out nodes, in the end, should remain as it is.

You may not alter the values in the list's nodes, only nodes themselves may be changed.

Example 1:

**Input:** head = [1,2,3,4,5], k = 2
**Output:** [2,1,4,3,5]

Example 2:

**Input:** head = [1,2,3,4,5], k = 3
**Output:** [3,2,1,4,5]

Constraints:

  • The number of nodes in the list is n.
  • 1 <= k <= n <= 5000
  • 0 <= Node.val <= 1000

Follow-up: Can you solve the problem in O(1) extra memory space?

这道题让我们以每k个为一组来翻转链表,实际上是把原链表分成若干小段,然后分别对其进行翻转,那么肯定总共需要两个函数,一个是用来分段的,一个是用来翻转的,以题目中给的例子来看,对于给定链表 1->2->3->4->5,一般在处理链表问题时,大多时候都会在开头再加一个 dummy node,因为翻转链表时头结点可能会变化,为了记录当前最新的头结点的位置而引入的 dummy node,加入 dummy node 后的链表变为 -1->1->2->3->4->5,如果k为3的话,目标是将 1,2,3 翻转一下,那么需要一些指针,pre 和 next 分别指向要翻转的链表的前后的位置,然后翻转后 pre 的位置更新到如下新的位置:

-1->1->2->3->4->5
 |        |  |
pre      cur next


-1->3->2->1->4->5  

|     |  |  

cur   pre next

以此类推,只要 cur 走过k个节点,那么 next 就是 cur->next,就可以调用翻转函数来进行局部翻转了,注意翻转之后新的 cur 和 pre 的位置都不同了,那么翻转之后,cur 应该更新为 pre->next,而如果不需要翻转的话,cur 更新为 cur->next,代码如下所示:

解法一:

class Solution {
public:
    ListNode* reverseKGroup(ListNode* head, int k) {
        if (!head || k == 1) return head;
        ListNode *dummy = new ListNode(-1), *pre = dummy, *cur = head;
        dummy->next = head;
        for (int i = 1; cur; ++i) {
            if (i % k == 0) {
                pre = reverseOneGroup(pre, cur->next);
                cur = pre->next;
            } else {
                cur = cur->next;
            }
        }
        return dummy->next;
    }
    ListNode* reverseOneGroup(ListNode* pre, ListNode* next) {
        ListNode *last = pre->next, *cur = last->next;
        while(cur != next) {
            last->next = cur->next;
            cur->next = pre->next;
            pre->next = cur;
            cur = last->next;
        }
        return last;
    }
};

我们也可以在一个函数中完成,首先遍历整个链表,统计出链表的长度,然后如果长度大于等于k,交换节点,当 k=2 时,每段只需要交换一次,当 k=3 时,每段需要交换2此,所以i从1开始循环,注意交换一段后更新 pre 指针,然后 num 自减k,直到 num<k 时循环结束,参见代码如下:

解法二:

class Solution {
public:
    ListNode* reverseKGroup(ListNode* head, int k) {
        ListNode *dummy = new ListNode(-1), *pre = dummy, *cur = pre;
        dummy->next = head;
        int num = 0;
        while (cur = cur->next) ++num;
        while (num >= k) {
            cur = pre->next;
            for (int i = 1; i < k; ++i) {
                ListNode *t = cur->next;
                cur->next = t->next;
                t->next = pre->next;
                pre->next = t;
            }
            pre = cur;
            num -= k;
        }
        return dummy->next;
    }
};

我们也可以使用递归来做,用 head 记录每段的开始位置,cur 记录结束位置的下一个节点,然后调用 reverse 函数来将这段翻转,然后得到一个 new_head,原来的 head 就变成了末尾,这时候后面接上递归调用下一段得到的新节点,返回 new_head 即可,参见代码如下:

解法三:

class Solution {
public:
    ListNode* reverseKGroup(ListNode* head, int k) {
        ListNode *cur = head;
        for (int i = 0; i < k; ++i) {
            if (!cur) return head;
            cur = cur->next;
        }
        ListNode *new_head = reverse(head, cur);
        head->next = reverseKGroup(cur, k);
        return new_head;
    }
    ListNode* reverse(ListNode* head, ListNode* tail) {
        ListNode *pre = tail;
        while (head != tail) {
            ListNode *t = head->next;
            head->next = pre;
            pre = head;
            head = t;
        }
        return pre;
    }
};

Github 同步地址:

#25

类似题目:

Swap Nodes in Pairs

参考资料:

https://leetcode.com/problems/reverse-nodes-in-k-group/

https://leetcode.com/problems/reverse-nodes-in-k-group/discuss/11435/C%2B%2B-Elegant-and-Small

https://leetcode.com/problems/reverse-nodes-in-k-group/discuss/11457/20-line-iterative-C%2B%2B-solution

https://leetcode.com/problems/reverse-nodes-in-k-group/discuss/11440/Non-recursive-Java-solution-and-idea

https://leetcode.com/problems/reverse-nodes-in-k-group/discuss/11423/Short-but-recursive-Java-code-with-comments

LeetCode All in One 题目讲解汇总(持续更新中...)

(欢迎加入博主的知识星球,博主将及时答疑解惑,并分享刷题经验与总结,快快加入吧~)

知识星球 喜欢请点赞,疼爱请打赏❤️~.~

微信打赏

|

Venmo 打赏


---|---

[LeetCode] 47. Permutations II


请点击下方图片观看讲解视频
Click below image to watch YouTube Video
Video

Given a collection of numbers, nums, that might contain duplicates, return all possible unique permutations in any order.

Example 1:

Input: nums = [1,1,2]
Output:
[[1,1,2],
 [1,2,1],
 [2,1,1]]

Example 2:

Input: nums = [1,2,3]
Output: [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

Constraints:

  • 1 <= nums.length <= 8
  • -10 <= nums[i] <= 10

这道题是之前那道 Permutations 的延伸,由于输入数组有可能出现重复数字,如果按照之前的算法运算,会有重复排列产生,我们要避免重复的产生,在递归函数中要判断前面一个数和当前的数是否相等,如果相等,且前一个数字的 visited 值为0的时候,必须跳过(下文中会解释这样做的原因),这样就不会产生重复排列了,代码如下:

解法一:

class Solution {
public:
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        vector<vector<int>> res;
        vector<int> cur, visited(nums.size(), 0);
        sort(nums.begin(), nums.end());
        dfs(nums, 0, visited, cur, res);
        return res;
    }
    void dfs(vector<int>& nums, int level, vector<int>& visited, vector<int>& cur, vector<vector<int>>& res) {
        if (level >= nums.size()) {
            res.push_back(cur); 
            return;
        }
        for (int i = 0; i < nums.size(); ++i) {
            if (visited[i] == 1) continue;
            if (i > 0 && nums[i] == nums[i - 1] && visited[i - 1] == 0) continue;
            visited[i] = 1;
            cur.push_back(nums[i]);
            dfs(nums, level + 1, visited, cur, res);
            cur.pop_back();
            visited[i] = 0;
        }
    }
};

在使用上面的方法的时候,一定要能弄清楚递归函数的 for 循环中两个 if 的剪枝的意思。在此之前,要弄清楚 level 的含义,这里用数组 cur 来拼排列结果,level 就是当前已经拼成的个数,其实就是 cur 数组的长度。我们看到,for 循环的起始是从0开始的,而本题的解法二,三,四都是用了一个 start 变量,从而 for 循环都是从 start 开始,一定要分清楚 start 和本解法中的 level 的区别。由于递归的 for 都是从0开始,难免会重复遍历到数字,而全排列不能重复使用数字,意思是每个 nums 中的数字在全排列中只能使用一次(当然这并不妨碍 nums 中存在重复数字)。不能重复使用数字就靠 visited 数组来保证,这就是第一个 if 剪枝的意义所在。关键来看第二个 if 剪枝的意义,这里说当前数字和前一个数字相同,且前一个数字的 visited 值为0的时候,必须跳过。这里的前一个数 visited 值为0,并不代表前一个数字没有被处理过,也可能是递归结束后恢复状态时将 visited 值重置为0了,实际上就是这种情况,下面打印了一些中间过程的变量值,给定数组 nums 为 [1 2 2],如下所示:

level = 0, i = 0 => cur: {}
level = 1, i = 0 => cur: {1 } skipped 1
level = 1, i = 1 => cur: {1 }
level = 2, i = 0 => cur: {1 2 } skipped 1
level = 2, i = 1 => cur: {1 2 } skipped 1
level = 2, i = 2 => cur: {1 2 }
level = 3 => saved  {1 2 2}
level = 3, i = 0 => cur: {1 2 2 } skipped 1
level = 3, i = 1 => cur: {1 2 2 } skipped 1
level = 3, i = 2 => cur: {1 2 2 } skipped 1
level = 2, i = 2 => cur: {1 2 2 } -> {1 2 } recovered
level = 1, i = 1 => cur: {1 2 } -> {1 } recovered
level = 1, i = 2 => cur: {1 } skipped 2
level = 0, i = 0 => cur: {1 } -> {} recovered
level = 0, i = 1 => cur: {}
level = 1, i = 0 => cur: {2 }
level = 2, i = 0 => cur: {2 1 } skipped 1
level = 2, i = 1 => cur: {2 1 } skipped 1
level = 2, i = 2 => cur: {2 1 }
level = 3 => saved  {2 1 2}
level = 3, i = 0 => cur: {2 1 2 } skipped 1
level = 3, i = 1 => cur: {2 1 2 } skipped 1
level = 3, i = 2 => cur: {2 1 2 } skipped 1
level = 2, i = 2 => cur: {2 1 2 } -> {2 1 } recovered
level = 1, i = 0 => cur: {2 1 } -> {2 } recovered
level = 1, i = 1 => cur: {2 } skipped 1
level = 1, i = 2 => cur: {2 }
level = 2, i = 0 => cur: {2 2 }
level = 3 => saved  {2 2 1}
level = 3, i = 0 => cur: {2 2 1 } skipped 1
level = 3, i = 1 => cur: {2 2 1 } skipped 1
level = 3, i = 2 => cur: {2 2 1 } skipped 1
level = 2, i = 0 => cur: {2 2 1 } -> {2 2 } recovered
level = 2, i = 1 => cur: {2 2 } skipped 1
level = 2, i = 2 => cur: {2 2 } skipped 1
level = 1, i = 2 => cur: {2 2 } -> {2 } recovered
level = 0, i = 1 => cur: {2 } -> {} recovered
level = 0, i = 2 => cur: {} skipped 2

注意看这里面的 skipped 1 表示的是第一个 if 剪枝起作用的地方,skipped 2 表示的是第二个 if 剪枝起作用的地方。我们主要关心的是第二个 if 剪枝,看上方第一个蓝色标记的那行,再上面的红色一行表示在 level = 1, i = 1 时递归调用结束后,恢复到起始状态,那么此时 out 数组中只有一个1,后面的2已经被 pop_back() 了,当然对应的 visited 值也重置为0了,这种情况下需要剪枝,当然不能再一次把2往里加,因为这种情况在递归中已经加入到结果 res 中了,所以到了 level = 1, i = 2 的状态时,nums[i] == nums[i-1] && visited[i-1] == 0 的条件满足了,剪枝就起作用了,这种重复的情况就被剪掉了。

还有一种比较简便的方法,在 Permutations 的基础上稍加修改,用 TreeSet 来保存结果,利用其不会有重复项的特点,然后在递归函数中 swap 的地方,判断如果i和 start 不相同,但是 nums[i] 和 nums[start] 相同的情况下跳过,继续下一个循环,参见代码如下:

解法二:

class Solution {
public:
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        set<vector<int>> res;
        permute(nums, 0, res);
        return vector<vector<int>> (res.begin(), res.end());
    }
    void permute(vector<int>& nums, int start, set<vector<int>>& res) {
        if (start >= nums.size()) res.insert(nums);
        for (int i = start; i < nums.size(); ++i) {
            if (i != start && nums[i] == nums[start]) continue;
            swap(nums[i], nums[start]);
            permute(nums, start + 1, res);
            swap(nums[i], nums[start]);
        }
    }
};

对于上面的解法,你可能会有疑问,我们不是在 swap 操作之前已经做了剪枝了么,为什么还是会有重复出现,以至于还要用 TreeSet 来取出重复呢。总感觉使用 TreeSet 去重复有点耍赖,可能并没有探究到本题深层次的内容。这是很好的想法,首先尝试将上面的 TreeSet 还原为 vector,并且在主函数调用递归之前给 nums 排个序(代码参见评论区三楼),然后测试一个最简单的例子:[1, 2, 2],得到的结果为:

[[1,2,2], [2,1,2], [2,2,1], [2,2,1], [2,1,2]]

我们发现有重复项,那么剪枝究竟在做些什么,怎么还是没法防止重复项的产生!那个剪枝只是为了防止当 start = 1, i = 2 时,将两个2交换,这样可以防止 {1, 2, 2} 被加入两次。但是没法防止其他的重复情况,要闹清楚为啥,需要仔细分析一些中间过程,下面打印了一些中间过程的变量:

start = 0, i = 0 => {1 2 2} 
start = 1, i = 1 => {1 2 2} 
start = 2, i = 2 => {1 2 2} 
start = 3 => saved  {1 2 2}
start = 1, i = 2 => {1 2 2} skipped
start = 0, i = 1 => {1 2 2} -> {2 1 2}
start = 1, i = 1 => {2 1 2} 
start = 2, i = 2 => {2 1 2} 
start = 3 => saved  {2 1 2}
start = 1, i = 2 => {2 1 2} -> {2 2 1}
start = 2, i = 2 => {2 2 1} 
start = 3 => saved  {2 2 1}
start = 1, i = 2 => {2 2 1} -> {2 1 2} recovered
start = 0, i = 1 => {2 1 2} -> {1 2 2} recovered
start = 0, i = 2 => {1 2 2} -> {2 2 1}
start = 1, i = 1 => {2 2 1} 
start = 2, i = 2 => {2 2 1} 
start = 3 => saved  {2 2 1}
start = 1, i = 2 => {2 2 1} -> {2 1 2}
start = 2, i = 2 => {2 1 2} 
start = 3 => saved  {2 1 2}
start = 1, i = 2 => {2 1 2} -> {2 2 1} recovered
start = 0, i = 2 => {2 2 1} -> {1 2 2} recovered

问题出在了递归调用之后的还原状态,参见上面的红色的两行,当 start = 0, i = 2 时,nums 已经还原到了 {1, 2, 2} 的状态,此时 nums[start] 不等于 nums[i],剪枝在这已经失效了,那么交换后的 {2, 2, 1} 还会被存到结果 res 中,而这个状态在之前就已经存过了一次。

注意到当 start = 0, i = 1 时,nums 交换之后变成了 {2, 1, 2},如果能保持这个状态,那么当 start = 0, i = 2 时,此时 nums[start] 就等于 nums[i] 了,剪枝操作就可以发挥作用了。怎么才能当递归结束后,不还原成为交换之前的状态的呢?答案就是不进行还原,这样还是能保存为之前交换后的状态。只是将最后一句 swap(nums[i], nums[start]) 删掉是不行的,因为递归函数的参数 nums 是加了&号,就表示引用了,那么之前调用递归函数之前的 nums 在递归函数中会被修改,可能还是无法得到我们想要的顺序,所以要把递归函数的 nums 参数的&号也同时去掉才行,参见代码如下:

解法三:

class Solution {
public:
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        vector<vector<int>> res;
        sort(nums.begin(), nums.end());
        permute(nums, 0, res);
        return res;
    }
    void permute(vector<int> nums, int start, vector<vector<int>>& res) {
        if (start >= nums.size()) res.push_back(nums);
        for (int i = start; i < nums.size(); ++i) {
            if (i != start && nums[i] == nums[start]) continue;
            swap(nums[i], nums[start]);
            permute(nums, start + 1, res);
        }
    }
};

好,再测试下 [1, 2, 2] 这个例子,并且把中间变量打印出来:

start = 0, i = 0 => {1 2 2} 
start = 1, i = 1 => {1 2 2} 
start = 2, i = 2 => {1 2 2} 
start = 3 => saved  {1 2 2}
start = 1, i = 2 => {1 2 2} skipped
start = 0, i = 1 => {1 2 2} -> {2 1 2}
start = 1, i = 1 => {2 1 2} 
start = 2, i = 2 => {2 1 2} 
start = 3 => saved  {2 1 2}
start = 1, i = 2 => {2 1 2} -> {2 2 1}
start = 2, i = 2 => {2 2 1} 
start = 3 => saved  {2 2 1}
start = 1, i = 2 => {2 2 1} recovered
start = 0, i = 1 => {2 1 2} recovered
start = 0, i = 2 => {2 1 2} skipped

明显发现短了许多,说明剪枝发挥了作用,看上面红色部分,当 start = 0, i = 1 时,递归函数调用完了之后,nums 数组保持了 {2, 1, 2} 的状态,那么到 start = 0, i = 2 的时候,nums[start] 就等于 nums[i] 了,剪枝操作就可以发挥作用了。

这时候你可能会想,调用完递归不恢复状态,感觉怪怪的,跟哥的递归模版不一样啊,容易搞混啊,而且一会加&号,一会不加的,这尼玛谁能分得清啊。别担心,I gotcha covered! 好,既然还是要恢复状态的话,就只能从剪枝入手了,原来那种 naive 的剪枝方法肯定无法使用,矛盾的焦点还是在于,当 start = 0, i = 2 时,nums 被还原成了 start = 0, i = 1 的交换前的状态 {1, 2, 2},这个状态已经被处理过了,再去处理一定会产生重复,怎么才知道这被处理过了呢,当前的 i = 2,需要往前去找是否有重复出现,由于数组已经排序过了,如果有重复,那么前面数一定和当前的相同,所以用一个 while 循环,往前找和 nums[i] 相同的数字,找到了就停下,当然如果小于 start 了也要停下,那么如果没有重复数字的话,j 一定是等于 start-1 的,那么如果不等于的话,就直接跳过就可以了,这样就可以去掉所有的重复啦,参见代码如下:

解法四:

class Solution {
public:
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        vector<vector<int>> res;
        sort(nums.begin(), nums.end());
        permute(nums, 0, res);
        return res;
    }
    void permute(vector<int>& nums, int start, vector<vector<int>>& res) {
        if (start >= nums.size()) res.push_back(nums);
        for (int i = start; i < nums.size(); ++i) {
            int j = i - 1;
            while (j >= start && nums[j] != nums[i]) --j;
            if (j != start - 1) continue;
            swap(nums[i], nums[start]);
            permute(nums, start + 1, res);
            swap(nums[i], nums[start]);
        }
    }
};

同样,我们再测试下 [1, 2, 2] 这个例子,并且把中间变量打印出来:

start = 0, i = 0 => {1 2 2} , j = -1
start = 1, i = 1 => {1 2 2} , j = 0
start = 2, i = 2 => {1 2 2} , j = 1
start = 3 => saved  {1 2 2}
start = 1, i = 2 => {1 2 2} skipped, j = 1
start = 0, i = 1 => {1 2 2} -> {2 1 2}, j = -1
start = 1, i = 1 => {2 1 2} , j = 0
start = 2, i = 2 => {2 1 2} , j = 1
start = 3 => saved  {2 1 2}
start = 1, i = 2 => {2 1 2} -> {2 2 1}, j = 0
start = 2, i = 2 => {2 2 1} , j = 1
start = 3 => saved  {2 2 1}
start = 1, i = 2 => {2 2 1} -> {2 1 2} recovered
start = 0, i = 1 => {2 1 2} -> {1 2 2} recovered
start = 0, i = 2 => {1 2 2} skipped, j = 1

到 start = 0, i = 2 的时候,j 此时等于1了,明显不是 start-1,说明有重复了,直接 skip 掉,这样剪枝操作就可以发挥作用了。

经过热心网友 xslin 提醒,我们也可以使用一个 HashSet 来跳过重复数字,在 for 循环前面新建一个 HashSet,然后对于遍历到的数字,先检测其是否已经在 HashSet 中了,是的话就跳过,否则就加入 HashSet,并进行交换,参见代码如下:

解法五:

class Solution {
public:
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        vector<vector<int>> res;
        sort(nums.begin(), nums.end());
        permute(nums, 0, res);
        return res;
    }
    void permute(vector<int>& nums, int start, vector<vector<int>>& res) {
        if (start >= nums.size()) res.push_back(nums);
        unordered_set<int> st;
        for (int i = start; i < nums.size(); ++i) {
            if (st.count(nums[i])) continue;
            st.insert(nums[i]);
            swap(nums[i], nums[start]);
            permute(nums, start + 1, res);
            swap(nums[i], nums[start]);
        }
    }
};

同样,我们再测试下 [1, 2, 2] 这个例子,并且把中间变量打印出来,可以看出来,跟上面解法的效果基本一样:

start = 0, i = 0 => {1 2 2} 
start = 1, i = 1 => {1 2 2} 
start = 2, i = 2 => {1 2 2} 
start = 3 => saved  {1 2 2}
start = 1, i = 2 => {1 2 2} skipped
start = 0, i = 1 => {1 2 2} -> {2 1 2}
start = 1, i = 1 => {2 1 2} 
start = 2, i = 2 => {2 1 2} 
start = 3 => saved  {2 1 2}
start = 1, i = 2 => {2 1 2} -> {2 2 1}
start = 2, i = 2 => {2 2 1} 
start = 3 => saved  {2 2 1}
start = 1, i = 2 => {2 2 1} -> {2 1 2} recovered
start = 0, i = 1 => {2 1 2} -> {1 2 2} recovered
start = 0, i = 2 => {1 2 2} skipped

之前的 Permutations 中的解法三也可以用在这里,只不过需要借助 TreeSet 来去重复,博主还未想出其他不用集合的去重复的方法,哪位看官大神们知道的话,请一定要留言告知博主,参见代码如下:

解法六:

class Solution {
public:
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        if (nums.empty()) return vector<vector<int>>(1, vector<int>());
        set<vector<int>> res;
        int first = nums[0];
        nums.erase(nums.begin());
        vector<vector<int>> words = permuteUnique(nums);
        for (auto &a : words) {
            for (int i = 0; i <= a.size(); ++i) {
                a.insert(a.begin() + i, first);
                res.insert(a);
                a.erase(a.begin() + i);
            }
        }   
        return vector<vector<int>> (res.begin(), res.end());
    }
};

之前的 Permutations 中的解法四博主没法成功修改使其可以通过这道题,即便是将结果 res 用 TreeSet 来去重复,还是不对。同样,哪位看官大神们知道的话,请一定要留言告知博主。后经过微信公众号上的热心网友 hahaboy 的提醒下,可以通过加上一个剪枝从而通过这道题,在最中间的 for 循环的最后,判断若 num 等于 t[i],直接 break 掉当前循环,否则会产生重复项,参见代码如下:

解法七:

class Solution {
public:
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        vector<vector<int>> res{{}};
        for (int num : nums) {
            for (int k = res.size(); k > 0; --k) {
                vector<int> t = res.front();
                res.erase(res.begin());
                for (int i = 0; i <= t.size(); ++i) {
                    vector<int> one = t;
                    one.insert(one.begin() + i, num);
                    res.push_back(one);
                    if (i < t.size() && num == t[i]) break;
                }
            }
        }
        return res;
    }
};

之前的 Permutations 中的解法五却可以原封不动的搬到这道题来,看来自带的 next_permutation() 函数就是叼啊,自带去重复功能,叼叼叼!参见代码如下:

解法八:

class Solution {
public:
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        vector<vector<int>> res;
        sort(nums.begin(), nums.end());
        res.push_back(nums);
        while (next_permutation(nums.begin(), nums.end())) {
            res.push_back(nums);
        }
        return res;
    }
};

Github 同步地址:

#47

类似题目:

Permutations

Next Permutation

Palindrome Permutation II

Number of Squareful Arrays

参考资料:

https://leetcode.com/problems/permutations-ii/

https://leetcode.com/problems/permutations-ii/discuss/18601/Short-iterative-Java-solution

https://leetcode.com/problems/permutations-ii/discuss/18596/A-simple-C%2B%2B-solution-in-only-20-lines

https://leetcode.com/problems/permutations-ii/discuss/18594/Really-easy-Java-solution-much-easier-than-the-solutions-with-very-high-vote

LeetCode All in One 题目讲解汇总(持续更新中...)

(欢迎加入博主的知识星球,博主将及时答疑解惑,并分享刷题经验与总结,快快加入吧~)

知识星球 喜欢请点赞,疼爱请打赏❤️~.~

微信打赏

|

Venmo 打赏


---|---

[LeetCode] 12. Integer to Roman


请点击下方图片观看讲解视频
Click below image to watch YouTube Video
Video

Roman numerals are represented by seven different symbols: I, V, X, L, C, D and M.

**Symbol       Value**
I             1
V             5
X             10
L             50
C             100
D             500
M             1000

For example, two is written as II in Roman numeral, just two one's added together. Twelve is written as, XII, which is simply X + II. The number twenty seven is written as XXVII, which is XX + V + II.

Roman numerals are usually written largest to smallest from left to right. However, the numeral for four is not IIII. Instead, the number four is written as IV. Because the one is before the five we subtract it making four. The same principle applies to the number nine, which is written as IX. There are six instances where subtraction is used:

  • I can be placed before V (5) and X (10) to make 4 and 9.
  • X can be placed before L (50) and C (100) to make 40 and 90.
  • C can be placed before D (500) and M(1000) to make 400 and 900.

Given an integer, convert it to a roman numeral. Input is guaranteed to be within the range from 1 to 3999.

Example 1:

Input: 3
Output: "III"

Example 2:

Input: 4
Output: "IV"

Example 3:

Input: 9
Output: "IX"

Example 4:

Input: 58
Output: "LVIII"
Explanation: L = 50, V = 5, III = 3.

Example 5:

Input: 1994
Output: "MCMXCIV"
Explanation: M = 1000, CM = 900, XC = 90 and IV = 4.

Constraints:

  • 1 <= num <= 3999

之前那篇文章写的是罗马数字转化成整数 Roman to Integer, 这次变成了整数转化成罗马数字,基本算法还是一样。由于题目中限定了输入数字的范围 (1 - 3999), 使得题目变得简单了不少。

I - 1

V - 5

X - 10

L - 50

C - 100

D - 500

M - 1000

例如整数 1437 的罗马数字为 MCDXXXVII, 我们不难发现,千位,百位,十位和个位上的数分别用罗马数字表示了。 1000 - M, 400 - CD, 30 - XXX, 7 - VII。所以我们要做的就是用取商法分别提取各个位上的数字,然后分别表示出来:

100 - C

200 - CC

300 - CCC

400 - CD

500 - D

600 - DC

700 - DCC

800 - DCCC

900 - CM

可以分为四类,100 到 300 一类,400 一类,500 到 800 一类,900 最后一类。每一位上的情况都是类似的,代码如下:

解法一:

class Solution {
public:
    string intToRoman(int num) {
        string res = "";
        vector<char> roman{'M', 'D', 'C', 'L', 'X', 'V', 'I'};
        vector<int> value{1000, 500, 100, 50, 10, 5, 1};
        for (int n = 0; n < 7; n += 2) {
            int x = num / value[n];
            if (x < 4) {
                for (int i = 1; i <= x; ++i) res += roman[n];
            } else if (x == 4) {
                res = res + roman[n] + roman[n - 1]; 
            } else if (x > 4 && x < 9) {
                res += roman[n - 1];
                for (int i = 6; i <= x; ++i) res += roman[n];
            } else if (x == 9) {
                res = res + roman[n] + roman[n - 2];
            }
            num %= value[n];            
        }
        return res;
    }
};

本题由于限制了输入数字范围这一特殊性,故而还有一种利用贪婪算法的解法,建立一个数表,每次通过查表找出当前最大的数,减去再继续查表,参见代码如下:

解法二:

class Solution {
public:
    string intToRoman(int num) {
        string res = "";
        vector<int> val{1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1};
        vector<string> str{"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"};
        for (int i = 0; i < val.size(); ++i) {
            while (num >= val[i]) {
                num -= val[i];
                res += str[i];
            }
        }
        return res;
    }
};

下面这种方法个人感觉属于比较投机取巧的方法,把所有的情况都列了出来,然后直接按位查表,O(1) 的时间复杂度啊,参见代码如下:

解法三:

class Solution {
public:
    string intToRoman(int num) {
        string res = "";
        vector<string> v1{"", "M", "MM", "MMM"};
        vector<string> v2{"", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"};
        vector<string> v3{"", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"};
        vector<string> v4{"", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"};
        return v1[num / 1000] + v2[(num % 1000) / 100] + v3[(num % 100) / 10] + v4[num % 10];
    }
};

Github 同步地址:

#12

类似题目:

Roman to Integer

Integer to English Words

参考资料:

https://leetcode.com/problems/integer-to-roman/

https://leetcode.com/problems/integer-to-roman/discuss/6274/simple-solution

https://leetcode.com/problems/integer-to-roman/discuss/6310/my-java-solution-easy-to-understand

LeetCode All in One 题目讲解汇总(持续更新中...)

(欢迎加入博主的知识星球,博主将及时答疑解惑,并分享刷题经验与总结,快快加入吧~)

知识星球 喜欢请点赞,疼爱请打赏❤️~.~

微信打赏

|

Venmo 打赏


---|---

[LeetCode] 27. Remove Element


请点击下方图片观看讲解视频
Click below image to watch YouTube Video
Video

Given an integer array nums and an integer val, remove all occurrences of val in nums in-place. The order of the elements may be changed. Then return the number of elements innums which are not equal toval.

Consider the number of elements in nums which are not equal to val be k, to get accepted, you need to do the following things:

  • Change the array nums such that the first k elements of nums contain the elements which are not equal to val. The remaining elements of nums are not important as well as the size of nums.
  • Return k.

Custom Judge:

The judge will test your solution with the following code:

int[] nums = [...]; // Input array
int val = ...; // Value to remove
int[] expectedNums = [...]; // The expected answer with correct length.
                            // It is sorted with no values equaling val.


int k = removeElement(nums, val); // Calls your implementation




assert k == expectedNums.length;  

sort(nums, 0, k); // Sort the first k elements of nums  

for (int i = 0; i < actualLength; i++) {  

assert nums[i] == expectedNums[i];  

}  

If all assertions pass, then your solution will be accepted.

Example 1:

**Input:** nums = [3,2,2,3], val = 3
**Output:** 2, nums = [2,2,_,_]
**Explanation:** Your function should return k = 2, with the first two elements of nums being 2.
It does not matter what you leave beyond the returned k (hence they are underscores).

Example 2:

**Input:** nums = [0,1,2,2,3,0,4,2], val = 2
**Output:** 5, nums = [0,1,4,0,3,_,_,_]
**Explanation:** Your function should return k = 5, with the first five elements of nums containing 0, 0, 1, 3, and 4.
Note that the five elements can be returned in any order.
It does not matter what you leave beyond the returned k (hence they are underscores).

Constraints:

  • 0 <= nums.length <= 100
  • 0 <= nums[i] <= 50
  • 0 <= val <= 100

这道题让我们移除一个数组中和给定值相同的数字,并返回新的数组的长度。是一道比较容易的题,只需要一个变量用来计数,然后遍历原数组,如果当前的值和给定值不同,就把当前值覆盖计数变量的位置,并将计数变量加1。代码如下:

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int res = 0;
        for (int i = 0; i < nums.size(); ++i) {
            if (nums[i] != val) nums[res++] = nums[i];
        }
        return res;
    }
};

Github 同步地址:

#27

类似题目:

Remove Duplicates from Sorted Array

Remove Linked List Elements

Move Zeroes

参考资料:

https://leetcode.com/problems/remove-element/

https://leetcode.com/problems/remove-element/discuss/12286/Accepted-java-solution

https://leetcode.com/problems/remove-element/discuss/12289/My-solution-for-your-reference.

LeetCode All in One 题目讲解汇总(持续更新中...)

(欢迎加入博主的知识星球,博主将及时答疑解惑,并分享刷题经验与总结,快快加入吧~)

知识星球 喜欢请点赞,疼爱请打赏❤️~.~

微信打赏

|

Venmo 打赏


---|---

[LeetCode] 23. Merge k Sorted Lists


请点击下方图片观看讲解视频
Click below image to watch YouTube Video
Video

You are given an array of k linked-lists lists, each linked-list is sorted in ascending order.

Merge all the linked-lists into one sorted linked-list and return it.

Example 1:

**Input:** lists = [[1,4,5],[1,3,4],[2,6]]
**Output:** [1,1,2,3,4,4,5,6]
**Explanation:** The linked-lists are:
[
  1->4->5,
  1->3->4,
  2->6
]
merging them into one sorted list:
1->1->2->3->4->4->5->6

Example 2:

**Input:** lists = []
**Output:** []

Example 3:

**Input:** lists = [[]]
**Output:** [] 

Constraints:

  • k == lists.length
  • 0 <= k <= 10^4
  • 0 <= lists[i].length <= 500
  • -10^4 <= lists[i][j] <= 10^4
  • lists[i] is sorted in ascending order.
  • The sum of lists[i].length will not exceed 10^4.

这道题让我们合并k个有序链表,最终合并出来的结果也必须是有序的,之前做过一道 Merge Two Sorted Lists,是混合插入两个有序链表。这道题增加了难度,变成合并k个有序链表了,但是不管合并几个,基本还是要两两合并。那么首先考虑的方法是能不能利用之前那道题的解法来解答此题。答案是肯定的,但是需要修改,怎么修改呢,最先想到的就是两两合并,就是前两个先合并,合并好了再跟第三个,然后第四个直到第k个。这样的思路是对的,但是效率不高,没法通过 OJ,所以只能换一种思路,这里就需要用到分治法 Divide and Conquer Approach。简单来说就是不停的对半划分,比如k个链表先划分为合并两个 k/2 个链表的任务,再不停的往下划分,直到划分成只有一个或两个链表的任务,开始合并。举个例子来说比如合并6个链表,那么按照分治法,首先分别合并0和3,1和4,2和5。这样下一次只需合并3个链表,再合并1和3,最后和2合并就可以了。代码中的k是通过 (n+1)/2 计算的,这里为啥要加1呢,这是为了当n为奇数的时候,k能始终从后半段开始,比如当 n=5 时,那么此时 k=3,则0和3合并,1和4合并,最中间的2空出来。当n是偶数的时候,加1也不会有影响,比如当 n=4 时,此时 k=2,那么0和2合并,1和3合并,完美解决问题,参见代码如下:

解法一:

class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        if (lists.empty()) return NULL;
        int n = lists.size();
        while (n > 1) {
            int k = (n + 1) / 2;
            for (int i = 0; i < n / 2; ++i) {
                lists[i] = mergeTwoLists(lists[i], lists[i + k]);
            }
            n = k;
        }
        return lists[0];
    }
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        ListNode *dummy = new ListNode(-1), *cur = dummy;
        while (l1 && l2) {
            if (l1->val < l2->val) {
                cur->next = l1;
                l1 = l1->next;
            } else {
                cur->next = l2;
                l2 = l2->next;
            }
            cur = cur->next;
        }
        if (l1) cur->next = l1;
        if (l2) cur->next = l2;
        return dummy->next;
    }
};

我们再来看另一种解法,这种解法利用了最小堆这种数据结构,首先把k个链表的首元素都加入最小堆中,它们会自动排好序。然后每次取出最小的那个元素加入最终结果的链表中,然后把取出元素的下一个元素再加入堆中,下次仍从堆中取出最小的元素做相同的操作,以此类推,直到堆中没有元素了,此时k个链表也合并为了一个链表,返回首节点即可,参见代码如下:

解法二:

class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        auto cmp = [](ListNode*& a, ListNode*& b) {
            return a->val > b->val;
        };
        priority_queue<ListNode*, vector<ListNode*>, decltype(cmp) > q(cmp);
        for (auto node : lists) {
            if (node) q.push(node);
        }
        ListNode *dummy = new ListNode(-1), *cur = dummy;
        while (!q.empty()) {
            auto t = q.top(); q.pop();
            cur->next = t;
            cur = cur->next;
            if (cur->next) q.push(cur->next);
        }
        return dummy->next;
    }
};

下面这种解法利用到了混合排序的**,也属于分治法的一种,做法是将原链表分成两段,然后对每段调用递归函数,suppose 返回的 left 和 right 已经合并好了,然后再对 left 和 right 进行合并,合并的方法就使用之前那道 Merge Two Sorted Lists 中的任意一个解法即可,这里使用了递归的写法,而本题解法一中用的是迭代的写法,参见代码如下:

解法三:

class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        return helper(lists, 0, (int)lists.size() - 1);
    }
    ListNode* helper(vector<ListNode*>& lists, int start, int end) {
        if (start > end) return NULL;
        if (start == end) return lists[start];
        int mid = start + (end - start) / 2;
        ListNode *left = helper(lists, start, mid);
        ListNode *right = helper(lists, mid + 1, end);
        return mergeTwoLists(left, right);
    }
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        if (!l1) return l2;
        if (!l2) return l1;
        if (l1->val < l2->val) {
            l1->next = mergeTwoLists(l1->next, l2);
            return l1;
        } else {
            l2->next = mergeTwoLists(l1, l2->next);
            return l2;
        }
    }
};

下面这种解法利用到了计数排序的**,由留言区二楼热心网友闽 A2436 提供,思路是将所有的结点值出现的最大值和最小值都记录下来,然后记录每个结点值出现的次数,这样从最小值遍历到最大值的时候,就会按顺序经过所有的结点值,根据其出现的次数,建立相对应个数的结点。但是这种解法有个特别需要注意的地方,那就是合并后的链表结点都是重新建立的,若在某些情况下,不能新建结点,而只能交换或者重新链接结点的话,那么此解法就不能使用,但好在本题并没有这种限制,可以完美过 OJ,参见代码如下:

解法四:

class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        ListNode *dummy = new ListNode(-1), *cur = dummy;
        unordered_map<int, int> m;
        int mx = INT_MIN, mn = INT_MAX;
        for (auto node : lists) {
            ListNode *t = node;
            while (t) {
                mx = max(mx, t->val);
                mn = min(mn, t->val);
                ++m[t->val];
                t = t->next;
            }
        }
        for (int i = mn; i <= mx; ++i) {
            if (!m.count(i)) continue;
            for (int j = 0; j < m[i]; ++j) {
                cur->next = new ListNode(i);
                cur = cur->next;
            }
        }
        return dummy->next;
    }
};

Github 同步地址:

#23

类似题目:

Merge Two Sorted Lists

Ugly Number II

Smallest Subarrays With Maximum Bitwise OR

参考资料:

https://leetcode.com/problems/merge-k-sorted-lists/

https://leetcode.com/problems/merge-k-sorted-lists/discuss/10640/Simple-Java-Merge-Sort

https://leetcode.com/problems/merge-k-sorted-lists/discuss/10528/A-java-solution-based-on-Priority-Queue

LeetCode All in One 题目讲解汇总(持续更新中...)

(欢迎加入博主的知识星球,博主将及时答疑解惑,并分享刷题经验与总结,快快加入吧~)

知识星球 喜欢请点赞,疼爱请打赏❤️~.~

微信打赏

|

Venmo 打赏


---|---

[LeetCode] 39. Combination Sum


请点击下方图片观看讲解视频
Click below image to watch YouTube Video
Video

Given an array of distinct integers candidates and a target integer target, return a list of all unique combinations ofcandidates where the chosen numbers sum totarget . You may return the combinations in any order.

The same number may be chosen from candidates an unlimited number of times. Two combinations are unique if the frequency of at least one of the chosen numbers is different.

The test cases are generated such that the number of unique combinations that sum up to target is less than 150 combinations for the given input.

Example 1:

Input: candidates = [2,3,6,7], target = 7
Output: [[2,2,3],[7]]
Explanation:
2 and 3 are candidates, and 2 + 2 + 3 = 7. Note that 2 can be used multiple times.
7 is a candidate, and 7 = 7.
These are the only two combinations.

Example 2:

Input: candidates = [2,3,5], target = 8
Output: [[2,2,2,2],[2,3,3],[3,5]]

Example 3:

Input: candidates = [2], target = 1
Output: []

Constraints:

  • 1 <= candidates.length <= 30
  • 2 <= candidates[i] <= 40
  • All elements of candidates are distinct.
  • 1 <= target <= 40

像这种结果要求返回所有符合要求解的题十有八九都是要利用到递归,而且解题的思路都大同小异,相类似的题目有 Path Sum IISubsets IIPermutationsPermutations IICombinations 等等,如果仔细研究这些题目发现都是一个套路,都是需要另写一个递归函数,这里我们新加入三个变量,start 记录当前的递归到的下标,cur 为一个解,res 保存所有已经得到的解,每次调用新的递归函数时,此时的 target 要减去当前数组的的数,具体看代码如下:

解法一:

class Solution {
public:
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        vector<vector<int>> res;
        vector<int> cur;
        dfs(candidates, target, 0, cur, res);
        return res;
    }
    void dfs(vector<int>& candidates, int target, int start, vector<int>& cur, vector<vector<int>>& res) {
        if (target < 0) return;
        if (target == 0) { res.push_back(cur); return; }
        for (int i = start; i < candidates.size(); ++i) {
            cur.push_back(candidates[i]);
            dfs(candidates, target - candidates[i], i, cur, res);
            cur.pop_back();
        }
    }
};

我们也可以不使用额外的函数,就在一个函数中完成递归,还是要先给数组排序,然后遍历,如果当前数字大于 target,说明肯定无法组成 target,由于排过序,之后的也无法组成 target,直接 break 掉。如果当前数字正好等于 target,则当前单个数字就是一个解,组成一个数组然后放到结果 res 中。然后将当前位置之后的数组取出来,调用递归函数,注意此时的 target 要减去当前的数字,然后遍历递归结果返回的二维数组,将当前数字加到每一个数组最前面,然后再将每个数组加入结果 res 即可,参见代码如下:

解法二:

class Solution {
public:
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        vector<vector<int>> res;
        sort(candidates.begin(), candidates.end());
        for (int i = 0; i < candidates.size(); ++i) {
            if (candidates[i] > target) break;
            if (candidates[i] == target) {res.push_back({candidates[i]}); break;}
            vector<int> vec = vector<int>(candidates.begin() + i, candidates.end());
            vector<vector<int>> tmp = combinationSum(vec, target - candidates[i]);
            for (auto a : tmp) {
                a.insert(a.begin(), candidates[i]);
                res.push_back(a);
            }
        }
        return res;
    }
};

我们也可以用迭代的解法来做,建立一个三维数组 dp,这里 dp[i] 表示目标数为 i 的所有解法集合。这里的i就从1遍历到 target 即可,对于每个i,都新建一个二维数组 cur,然后遍历 candidates 数组,如果遍历到的数字大于i,说明当前及之后的数字都无法组成i,直接 break 掉。否则如果相等,那么把当前数字自己组成一个数组,并且加到 cur 中。否则就遍历 dp[i - candidates[j]] 中的所有数组,如果当前数字大于数组的首元素,则跳过,因为结果要求是要有序的。否则就将当前数字加入数组的开头,并且将数组放入 cur 之中即可,参见代码如下:

解法三:

class Solution {
public:
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        vector<vector<vector<int>>> dp(target + 1);
        sort(candidates.begin(), candidates.end());
        for (int i = 1; i <= target; ++i) {
            vector<vector<int>> cur;
            for (int j = 0; j < candidates.size(); ++j) {
                if (candidates[j] > i) break;
                if (candidates[j] == i) {
                    cur.push_back({candidates[j]}); 
                    break;
                }
                for (auto a : dp[i - candidates[j]]) {
                    if (candidates[j] > a[0]) continue;
                    a.insert(a.begin(), candidates[j]);
                    cur.push_back(a);
                }
            }
            dp[i] = cur;
        }
        return dp[target];
    }
};

Github 同步地址:

#39

类似题目:

Combination Sum III

Combination Sum II

Combination Sum IV

Combinations

Factor Combinations

Letter Combinations of a Phone Number

参考资料:

https://leetcode.com/problems/combination-sum/

https://leetcode.com/problems/combination-sum/discuss/16825/Recursive-java-solution

https://leetcode.com/problems/combination-sum/discuss/16509/Iterative-Java-DP-solution

https://leetcode.com/problems/combination-sum/discuss/16502/A-general-approach-to-backtracking-questions-in-Java-(Subsets-Permutations-Combination-Sum-Palindrome-Partitioning)

LeetCode All in One 题目讲解汇总(持续更新中...)

(欢迎加入博主的知识星球,博主将及时答疑解惑,并分享刷题经验与总结,快快加入吧~)

知识星球 喜欢请点赞,疼爱请打赏❤️~.~

微信打赏

|

Venmo 打赏


---|---

[LeetCode] 29. Divide Two Integers


请点击下方图片观看讲解视频
Click below image to watch YouTube Video
Video

Given two integers dividend and divisor, divide two integers without using multiplication, division, and mod operator.

The integer division should truncate toward zero, which means losing its fractional part. For example, 8.345 would be truncated to 8, and -2.7335 would be truncated to -2.

Return _the quotient after dividing _dividend bydivisor.

Note: Assume we are dealing with an environment that could only store integers within the 32-bit signed integer range: [−2^31, 2^31 − 1]. For this problem, if the quotient is strictly greater than 2^31 - 1, then return 2^31 - 1, and if the quotient is strictly less than -2^31, then return -2^31.

Example 1:

**Input:** dividend = 10, divisor = 3
**Output:** 3
**Explanation:** 10/3 = 3.33333.. which is truncated to 3.

Example 2:

**Input:** dividend = 7, divisor = -3
**Output:** -2
**Explanation:** 7/-3 = -2.33333.. which is truncated to -2.

Constraints:

  • -2^31 <= dividend, divisor <= 2^31 - 1
  • divisor != 0

这道题让我们求两数相除,而且规定不能用乘法,除法和取余操作,那么这里可以用另一神器位操作 Bit Manipulation,思路是,如果被除数大于或等于除数,则进行如下循环,定义变量t等于除数,定义计数p,当t的两倍小于等于被除数时,进行如下循环,t扩大一倍,p扩大一倍,然后更新 res 和m。这道题的 OJ 给的一些 test case 非常的讨厌,因为输入的都是 int 型,比如被除数是 -2147483648,在 int 范围内,当除数是 -1 时,结果就超出了 int 范围,需要返回 INT_MAX,所以对于这种情况就在开始用 if 判定,返回 INT_MAX。然后还要根据被除数和除数的正负来确定返回值的正负,这里采用长整型 long 来完成所有的计算,最后返回值乘以符号即可。其实这道题的本质还是不停的成倍数扩大除数,虽然题目说了不能用乘法,但是这里用了位操作的左移来巧妙地规避了这一点,因为左移一位就相当于乘以2。

这里的外层 while 循环是判断被除数大于等于除数,然后里面定义了个临时变量t,初始化为除数n,还有表示临时商的p,初始化为1。内层的 while 循环就是通过不停扩大除数,来找到临时商的最大值,比如若被除数 m=56,除数 n=5 的话,此时经过内层 while 循环后,t=40,p=8,此时t再翻倍的话,就会超过被除数了,所以就到这里,然后更新结果 res 和被除数m的值。下一轮循环时被除数 m=16,除数 n=5,此时经过内层 while 循环后,t=10,p=2,此时t再翻倍的话,就会超过被除数了,所以就到这里,然后更新结果 res 和被除数m的值。下一轮循环时被除数 m=6,除数 n=5,此时经过内层 while 循环后,t=5,p=1,此时t再翻倍的话,就会超过被除数了,所以就到这里,然后更新结果 res 和被除数m的值。这时候被除数 m=1,除数 n=5,被除数小于除数了,跳出外层 while 循环,返回结果 res=11 即可,参见代码如下:

解法一:

class Solution {
public:
    int divide(int dividend, int divisor) {
        if (dividend == INT_MIN && divisor == -1) return INT_MAX;
        long m = labs(dividend), n = labs(divisor), res = 0;
        int sign = ((dividend < 0) ^ (divisor < 0)) ? -1 : 1;
        if (n == 1) return sign == 1 ? m : -m;
        while (m >= n) {
            long t = n, p = 1;
            while (m >= (t << 1)) {
                t <<= 1;
                p <<= 1;
            }
            res += p;
            m -= t;
        }
        return sign == 1 ? res : -res;
    }
};

我们可以通过递归的方法来解使上面的解法变得更加简洁:

解法二:

class Solution {
public:
    int divide(int dividend, int divisor) {
        long m = labs(dividend), n = labs(divisor), res = 0;
        if (m < n) return 0;
        long t = n, p = 1;
        while (m >= (t << 1)) {
            t <<= 1;
            p <<= 1;
        }
        res += p + divide(m - t, n);
        if ((dividend < 0) ^ (divisor < 0)) res = -res;
        return res > INT_MAX ? INT_MAX : res;
    }
};

Github 同步地址:

#29

参考资料:

https://leetcode.com/problems/divide-two-integers/

https://leetcode.com/problems/divide-two-integers/discuss/13524/summary-of-3-c-solutions

https://leetcode.com/problems/divide-two-integers/discuss/13407/C%2B%2B-bit-manipulations

https://leetcode.com/problems/divide-two-integers/discuss/142849/C%2B%2BJavaPython-Should-Not-Use-%22long%22-Int

LeetCode All in One 题目讲解汇总(持续更新中...)

(欢迎加入博主的知识星球,博主将及时答疑解惑,并分享刷题经验与总结,快快加入吧~)

知识星球 喜欢请点赞,疼爱请打赏❤️~.~

微信打赏

|

Venmo 打赏


---|---

[LeetCode] 31. Next Permutation


请点击下方图片观看讲解视频
Click below image to watch YouTube Video
Video

A permutation of an array of integers is an arrangement of its members into a sequence or linear order.

  • For example, for arr = [1,2,3], the following are all the permutations of arr: [1,2,3], [1,3,2], [2, 1, 3], [2, 3, 1], [3,1,2], [3,2,1].

The next permutation of an array of integers is the next lexicographically greater permutation of its integer. More formally, if all the permutations of the array are sorted in one container according to their lexicographical order, then the next permutation of that array is the permutation that follows it in the sorted container. If such arrangement is not possible, the array must be rearranged as the lowest possible order (i.e., sorted in ascending order).

  • For example, the next permutation of arr = [1,2,3] is [1,3,2].
  • Similarly, the next permutation of arr = [2,3,1] is [3,1,2].
  • While the next permutation of arr = [3,2,1] is [1,2,3] because [3,2,1] does not have a lexicographical larger rearrangement.

Given an array of integers nums, find the next permutation of nums.

The replacement must be in place and use only constant extra memory.

Example 1:

**Input:** nums = [1,2,3]
**Output:** [1,3,2]

Example 2:

**Input:** nums = [3,2,1]
**Output:** [1,2,3]

Example 3:

**Input:** nums = [1,1,5]
**Output:** [1,5,1]

Constraints:

  • 1 <= nums.length <= 100
  • 0 <= nums[i] <= 100

这道题让我们求下一个排列顺序,由题目中给的例子可以看出来,如果给定数组是降序,则说明是全排列的最后一种情况,则下一个排列就是最初始情况,可以参见之前的博客 Permutations。再来看下面一个例子,有如下的一个数组

1 2 7 4 3 1

下一个排列为:

1 3 1 2 4 7

那么是如何得到的呢,我们通过观察原数组可以发现,如果从末尾往前看,数字逐渐变大,到了2时才减小的,然后再从后往前找第一个比2大的数字,是3,那么我们交换2和3,再把此时3后面的所有数字转置一下即可,步骤如下:

1 2 7 4 3 1

1 2 7 4 3 1

1 3 7 4 2 1

1 3 1 2 4 7

解法一:

class Solution {
public:
    void nextPermutation(vector<int> &num) {
        int i, j, n = num.size();
        for (i = n - 2; i >= 0; --i) {
            if (num[i + 1] > num[i]) {
                for (j = n - 1; j > i; --j) {
                    if (num[j] > num[i]) break;
                }
                swap(num[i], num[j]);
                reverse(num.begin() + i + 1, num.end());
                return;
            }
        }
        reverse(num.begin(), num.end());
    }
};

下面这种写法更简洁一些,但是整体思路和上面的解法没有什么区别,参见代码如下:

解法二:

class Solution {
public:
    void nextPermutation(vector<int>& nums) {
        int n = nums.size(), i = n - 2, j = n - 1;
        while (i >= 0 && nums[i] >= nums[i + 1]) --i;
        if (i >= 0) {
            while (nums[j] <= nums[i]) --j;
            swap(nums[i], nums[j]);
        }
        reverse(nums.begin() + i + 1, nums.end());
    }
};

Github 同步地址:

#31

类似题目:

Permutations II

Permutations

Permutation Sequence

Palindrome Permutation II

Palindrome Permutation

Minimum Adjacent Swaps to Reach the Kth Smallest Number

参考资料:

https://leetcode.com/problems/next-permutation/

https://leetcode.com/problems/next-permutation/discuss/13921/1-4-11-lines-C%2B%2B

https://leetcode.com/problems/next-permutation/discuss/13867/C%2B%2B-from-Wikipedia

LeetCode All in One 题目讲解汇总(持续更新中...)

(欢迎加入博主的知识星球,博主将及时答疑解惑,并分享刷题经验与总结,快快加入吧~)

知识星球 喜欢请点赞,疼爱请打赏❤️~.~

微信打赏

|

Venmo 打赏


---|---

[LeetCode] 45. Jump Game II


请点击下方图片观看讲解视频
Click below image to watch YouTube Video
Video

You are given a 0-indexed array of integers nums of length n. You are initially positioned at nums[0].

Each element nums[i] represents the maximum length of a forward jump from index i. In other words, if you are at nums[i], you can jump to any nums[i + j] where:

  • 0 <= j <= nums[i] and
  • i + j < n

Return the minimum number of jumps to reachnums[n - 1]. The test cases are generated such that you can reach nums[n - 1].

Example 1:

Input: nums = [2,3,1,1,4]
Output: 2
Explanation: The minimum number of jumps to reach the last index is 2. Jump 1 step from index 0 to 1, then 3 steps to the last index.

Example 2:

Input: nums = [2,3,0,1,4]
Output: 2

Constraints:

  • 1 <= nums.length <= 10^4
  • 0 <= nums[i] <= 1000
  • It's guaranteed that you can reach nums[n - 1].

这题是之后那道 Jump Game 的延伸,那题是问能不能到达最后一个数字,而此题只让求到达最后一个位置的最少跳跃数,貌似是默认一定能到达最后位置的? 题目中说了起始位置在 nums[0],并且数组中的每个数字表示当前的跳力,即若 nums[i] 的位置上的数字是j,则可以跳跃的范围是 [i, i + j],当然 i+j 需要小于n,否则会越界。此题的核心方法是利用贪婪算法 Greedy 的**来解,想想为什么呢? 为了较快的跳到末尾,想知道每一步能跳的范围,这里贪婪并不是要在能跳的范围中选跳力最远的那个位置,因为这样选下来不一定是最优解,这么一说感觉又有点不像贪婪算法了。

其实这里贪的是一个能到达的最远范围,遍历当前跳跃能到的所有位置,然后根据该位置上的跳力来预测下一步能跳到的最远距离,贪出一个最远的范围,一旦当这个范围到达末尾时,当前所用的步数一定是最小步数。需要两个变量 cur 和 pre 分别来保存当前的能到达的最远位置和之前能到达的最远位置,只要 cur 未达到最后一个位置则循环继续,pre 先赋值为 cur 的值,表示上一次循环后能到达的最远位置,如果当前位置i小于等于 pre,说明还是在上一跳能到达的范围内,根据当前位置加跳力来更新 cur,更新 cur 的方法是比较当前的 cur 和 i + nums[i] 之中的较大值,如果题目中未说明是否能到达末尾,还可以判断此时 pre 和 cur 是否相等,如果相等说明 cur 没有更新,即无法到达末尾位置,返回 -1,代码如下:

解法一:

class Solution {
public:
    int jump(vector<int>& nums) {
        int res = 0, n = nums.size(), i = 0, cur = 0;
        while (cur < n - 1) {
            ++res;
            int pre = cur;
            for (; i <= pre; ++i) {
                cur = max(cur, i + nums[i]);
            }
            if (pre == cur) return -1; // May not need this
        }
        return res;
    }
};

还有一种写法,跟上面那解法略有不同,但是本质的**还是一样的,关于此解法的详细分析可参见网友 实验室小纸贴校外版的博客,这里 cur 是当前能到达的最远位置,last 是上一步能到达的最远位置,遍历数组,首先用 i + nums[i] 更新 cur,这个在上面解法中讲过了,然后判断如果当前位置到达了 last,即上一步能到达的最远位置,说明需要再跳一次了,将 last 赋值为 cur,并且步数 res 自增1,这里小优化一下,判断如果 cur 到达末尾了,直接 break 掉即可,代码如下:

解法二:

class Solution {
public:
    int jump(vector<int>& nums) {
        int res = 0, n = nums.size(), last = 0, cur = 0;
        for (int i = 0; i < n - 1; ++i) {
            cur = max(cur, i + nums[i]);
            if (i == last) {
                last = cur;
                ++res;
                if (cur >= n - 1) break;
            }
        }
        return res;
    }
};

Github 同步地址:

#45

类似题目:

Jump Game

Jump Game III

Jump Game IV

Jump Game V

Jump Game VIII

Minimum Number of Visited Cells in a Grid

Maximum Number of Jumps to Reach the Last Index

Visit Array Positions to Maximize Score

参考资料:

https://leetcode.com/problems/jump-game-ii/

https://leetcode.com/problems/jump-game-ii/discuss/18028/O(n)-BFS-solution

https://leetcode.com/problems/jump-game-ii/discuss/18023/Single-loop-simple-java-solution

LeetCode All in One 题目讲解汇总(持续更新中...)

(欢迎加入博主的知识星球,博主将及时答疑解惑,并分享刷题经验与总结,快快加入吧~)

知识星球 喜欢请点赞,疼爱请打赏❤️~.~

微信打赏

|

Venmo 打赏


---|---

[LeetCode] 41. First Missing Positive


请点击下方图片观看讲解视频
Click below image to watch YouTube Video
Video

Given an unsorted integer array nums, return the smallest missing positive integer.

You must implement an algorithm that runs in O(n) time and uses O(1) auxiliary space.

Example 1:

Input: nums = [1,2,0]
Output: 3
Explanation: The numbers in the range [1,2] are all in the array.

Example 2:

Input: nums = [3,4,-1,1]
Output: 2
Explanation: 1 is in the array but 2 is missing.

Example 3:

Input: nums = [7,8,9,11,12]
Output: 1
Explanation: The smallest positive integer 1 is missing.

Constraints:

  • 1 <= nums.length <= 10^5
  • -2^31 <= nums[i] <= 2^31 - 1

这道题让我们找缺失的首个正数,由于限定了 O(n) 的时间,所以一般的排序方法都不能用,最开始博主没有看到还限制了空间复杂度,所以想到了用 HashSet 来解,这个思路很简单,把所有的数都存入 HashSet 中,然后循环从1开始递增找数字,哪个数字找不到就返回哪个数字,如果一直找到了最大的数字(这里是 nums 数组的长度),则加1后返回结果 res,参见代码如下:

解法一:

// NOT constant space
class Solution {
public:
    int firstMissingPositive(vector<int>& nums) {
        unordered_set<int> st(nums.begin(), nums.end());
        int res = 1, n = nums.size();
        while (res <= n) {
            if (!st.count(res)) return res;
            ++res;
        }
        return res;
    }
};

但是上面的解法不是 O(1) 的空间复杂度,所以需要另想一种解法,既然不能建立新的数组,那么只能覆盖原有数组,思路是把1放在数组第一个位置 nums[0],2放在第二个位置 nums[1],即需要把 nums[i] 放在 nums[nums[i] - 1]上,遍历整个数组,如果 nums[i] != i + 1, 而 nums[i] 为整数且不大于n,另外 nums[i] 不等于 nums[nums[i] - 1] 的话,将两者位置调换,如果不满足上述条件直接跳过,最后再遍历一遍数组,如果对应位置上的数不正确则返回正确的数,参见代码如下:

解法二:

class Solution {
public:
    int firstMissingPositive(vector<int>& nums) {
        int n = nums.size();
        for (int i = 0; i < n; ++i) {
            while (nums[i] > 0 && nums[i] <= n && nums[nums[i] - 1] != nums[i]) {
                swap(nums[i], nums[nums[i] - 1]);
            }
        }
        for (int i = 0; i < n; ++i) {
            if (nums[i] != i + 1) return i + 1;
        }
        return n + 1;
    }
};

Github 同步地址:

#41

类似题目:

Missing Number

Find the Duplicate Number

Find All Numbers Disappeared in an Array

Couples Holding Hands

Smallest Number in Infinite Set

Maximum Number of Integers to Choose From a Range I

Smallest Missing Non-negative Integer After Operations

Maximum Number of Integers to Choose From a Range II

参考资料:

https://leetcode.com/problems/first-missing-positive/

https://leetcode.com/problems/first-missing-positive/discuss/17071/My-short-c++-solution-O(1)-space-and-O(n)-time

LeetCode All in One 题目讲解汇总(持续更新中...)

(欢迎加入博主的知识星球,博主将及时答疑解惑,并分享刷题经验与总结,快快加入吧~)

知识星球 喜欢请点赞,疼爱请打赏❤️~.~

微信打赏

|

Venmo 打赏


---|---

[LeetCode] 15. 3Sum


请点击下方图片观看讲解视频
Click below image to watch YouTube Video
Video

Given an integer array nums, return all the triplets [nums[i], nums[j], nums[k]] such that i != j, i != k, and j != k, and nums[i] + nums[j] + nums[k] == 0.

Notice that the solution set must not contain duplicate triplets.

Example 1:

**Input:** nums = [-1,0,1,2,-1,-4]
**Output:** [[-1,-1,2],[-1,0,1]]
**Explanation:** 
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0.
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0.
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0.
The distinct triplets are [-1,0,1] and [-1,-1,2].
Notice that the order of the output and the order of the triplets does not matter.

Example 2:

**Input:** nums = [0,1,1]
**Output:** []
**Explanation:** The only possible triplet does not sum up to 0.

Example 3:

**Input:** nums = [0,0,0]
**Output:** [[0,0,0]]
**Explanation:** The only possible triplet sums up to 0.

Constraints:

  • 3 <= nums.length <= 3000
  • -105 <= nums[i] <= 105

这道题让我们求三数之和,比之前那道 Two Sum 要复杂一些,博主考虑过先 fix 一个数,然后另外两个数使用 Two Sum 那种 HashMap 的解法,但是会有重复结果出现,就算使用 TreeSet 来去除重复也不行,会 TLE,看来此题并不是考 Two Sum 的解法。来分析一下这道题的特点,要找出三个数且和为0,那么除了三个数全是0的情况之外,肯定会有负数和正数,还是要先 fix 一个数,然后去找另外两个数,只要找到两个数且和为第一个 fix 数的相反数就行了,既然另外两个数不能使用 Two Sum 的那种解法来找,如何能更有效的定位呢?我们肯定不希望遍历所有两个数的组合吧,所以如果数组是有序的,那么就可以用双指针以线性时间复杂度来遍历所有满足题意的两个数组合。

对原数组进行排序,然后开始遍历排序后的数组,这里注意不是遍历到最后一个停止,而是到倒数第三个就可以了。这里可以先做个剪枝优化,就是当遍历到正数的时候就 break,为啥呢,因为数组现在是有序的了,如果第一个要 fix 的数就是正数了,则后面的数字就都是正数,就永远不会出现和为0的情况了。然后还要加上重复就跳过的处理,处理方法是从第二个数开始,如果和前面的数字相等,就跳过,因为不想把相同的数字fix两次。对于遍历到的数,用0减去这个 fix 的数得到一个 target,然后只需要再之后找到两个数之和等于 target 即可。用两个指针分别指向 fix 数字之后开始的数组首尾两个数,如果两个数和正好为 target,则将这两个数和 fix 的数一起存入结果中。然后就是跳过重复数字的步骤了,两个指针都需要检测重复数字。如果两数之和小于 target,则将左边那个指针i右移一位,使得指向的数字增大一些。同理,如果两数之和大于 target,则将右边那个指针j左移一位,使得指向的数字减小一些,代码如下:

解法一:

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> res;
        sort(nums.begin(), nums.end());
        if (nums.empty() || nums.back() < 0 || nums.front() > 0) return {};
        for (int k = 0; k < (int)nums.size() - 2; ++k) {
            if (nums[k] > 0) break;
            if (k > 0 && nums[k] == nums[k - 1]) continue;
            int target = 0 - nums[k], i = k + 1, j = (int)nums.size() - 1;
            while (i < j) {
                if (nums[i] + nums[j] == target) {
                    res.push_back({nums[k], nums[i], nums[j]});
                    while (i < j && nums[i] == nums[i + 1]) ++i;
                    while (i < j && nums[j] == nums[j - 1]) --j;
                    ++i; --j;
                } else if (nums[i] + nums[j] < target) ++i;
                else --j;
            }
        }
        return res;
    }
};

或者我们也可以利用 TreeSet 的不能包含重复项的特点来防止重复项的产生,那么就不需要检测数字是否被 fix 过两次,不过个人觉得还是前面那种解法更好一些,参见代码如下:

解法二:

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        set<vector<int>> res;
        sort(nums.begin(), nums.end());
        if (nums.empty() || nums.back() < 0 || nums.front() > 0) return {};
        for (int k = 0; k < (int)nums.size() - 2; ++k) {
            if (nums[k] > 0) break;
            int target = 0 - nums[k], i = k + 1, j = (int)nums.size() - 1;
            while (i < j) {
                if (nums[i] + nums[j] == target) {
                    res.insert({nums[k], nums[i], nums[j]});
                    while (i < j && nums[i] == nums[i + 1]) ++i;
                    while (i < j && nums[j] == nums[j - 1]) --j;
                    ++i; --j;
                } else if (nums[i] + nums[j] < target) ++i;
                else --j;
            }
        }
        return vector<vector<int>>(res.begin(), res.end());
    }
};

Github 同步地址:

#15

类似题目:

Two Sum

3Sum Smaller

3Sum Closest

4Sum

参考资料:

https://leetcode.com/problems/3sum/

https://leetcode.com/problems/3sum/discuss/7380/Concise-O(N2)-Java-solution

https://leetcode.com/problems/3sum/discuss/7373/Share-my-simple-java-solution

http://www.lifeincode.net/programming/leetcode-two-sum-3-sum-3-sum-closest-and-4-sum-java/

LeetCode All in One 题目讲解汇总(持续更新中...)

(欢迎加入博主的知识星球,博主将及时答疑解惑,并分享刷题经验与总结,快快加入吧~)

知识星球 喜欢请点赞,疼爱请打赏❤️~.~

微信打赏

|

Venmo 打赏


---|---

[LeetCode] 46. Permutations


请点击下方图片观看讲解视频
Click below image to watch YouTube Video
Video

Given an array nums of distinct integers, return all the possible permutations. You can return the answer in any order.

Example 1:

Input: nums = [1,2,3]
Output: [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

Example 2:

Input: nums = [0,1]
Output: [[0,1],[1,0]]

Example 3:

Input: nums = [1]
Output: [[1]] 

Constraints:

  • 1 <= nums.length <= 6
  • -10 <= nums[i] <= 10
  • All the integers of nums are unique.

这道题是求全排列问题,给的输入数组没有重复项,这跟之前的那道 Combinations 和类似,解法基本相同,但是不同点在于那道不同的数字顺序只算一种,是一道典型的组合题,而此题是求全排列问题,还是用递归 DFS 来求解。这里需要用到一个 visited 数组来标记某个数字是否访问过,然后在 DFS 递归函数从的循环应从头开始,而不是从 level 开始,这是和 Combinations 不同的地方,其余思路大体相同。这里再说下 level 吧,其本质是记录当前已经拼出的个数,一旦其达到了 nums 数组的长度,说明此时已经是一个全排列了,因为再加数字的话,就会超出。还有就是,为啥这里的 level 要从0开始遍历,因为这是求全排列,每个位置都可能放任意一个数字,这样会有个问题,数字有可能被重复使用,由于全排列是不能重复使用数字的,所以需要用一个 visited 数组来标记某个数字是否使用过,代码如下:

解法一:

class Solution {
public:
    vector<vector<int>> permute(vector<int>& nums) {
        vector<vector<int>> res;
        vector<int> cur, visited(nums.size());
        dfs(nums, 0, visited, cur, res);
        return res;
    }
    void dfs(vector<int>& nums, int level, vector<int>& visited, vector<int>& cur, vector<vector<int>>& res) {
        if (level == nums.size()) {
            res.push_back(cur); 
            return;
        }
        for (int i = 0; i < nums.size(); ++i) {
            if (visited[i] == 1) continue;
            visited[i] = 1;
            cur.push_back(nums[i]);
            dfs(nums, level + 1, visited, cur, res);
            cur.pop_back();
            visited[i] = 0;
        }
    }
};

上述解法的最终生成顺序为:[[1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1]] 。

还有一种递归的写法,更简单一些,这里是每次交换 nums 里面的两个数字,经过递归可以生成所有的排列情况。这里你可能注意到,为啥在递归函数中, push_back() 了之后没有返回呢,而解法一或者是 Combinations 的递归解法在更新结果 res 后都 return 了呢?其实如果你仔细看代码的话,此时 start 已经大于等于 nums.size() 了,而下面的 for 循环的i是从 start 开始的,根本就不会执行 for 循环里的内容,就相当于 return 了,博主偷懒就没写了。但其实为了避免混淆,最好还是加上,免得和前面的搞混了,代码如下:

解法二:

class Solution {
public:
    vector<vector<int>> permute(vector<int>& nums) {
        vector<vector<int>> res;
        dfs(nums, 0, res);
        return res;
    }
    void dfs(vector<int>& nums, int start, vector<vector<int>>& res) {
        if (start >= nums.size()) res.push_back(nums);
        for (int i = start; i < nums.size(); ++i) {
            swap(nums[start], nums[i]);
            dfs(nums, start + 1, res);
            swap(nums[start], nums[i]);
        }
    }
};

上述解法的最终生成顺序为:[[1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,2,1], [3,1,2]]

再来看一种方法,这种方法是 CareerCup 书上的方法,也挺不错的,这道题是**是这样的:

当 n=1 时,数组中只有一个数 a1,其全排列只有一种,即为 a1

当 n=2 时,数组中此时有 a1a2,其全排列有两种,a1a2 和 a2a1,那么此时考虑和上面那种情况的关系,可以发现,其实就是在 a1 的前后两个位置分别加入了 a2

当 n=3 时,数组中有 a1a2a3,此时全排列有六种,分别为 a1a2a3, a1a3a2, a2a1a3, a2a3a1, a3a1a2, 和 a3a2a1。那么根据上面的结论,实际上是在 a1a2 和 a2a1 的基础上在不同的位置上加入 a3 而得到的。

_ a1 _ a2 _ : a3a1a2, a1a3a2, a1a2a3

_ a2 _ a1 _ : a3a2a1, a2a3a1, a2a1a3

解法三:

class Solution {
public:
    vector<vector<int>> permute(vector<int>& nums) {
        if (nums.empty()) return vector<vector<int>>(1);
        vector<vector<int>> res;
        int first = nums[0];
        nums.erase(nums.begin());
        vector<vector<int>> words = permute(nums);
        for (auto &a : words) {
            for (int i = 0; i <= a.size(); ++i) {
                a.insert(a.begin() + i, first);
                res.push_back(a);
                a.erase(a.begin() + i);
            }
        }   
        return res;
    }
};

上述解法的最终生成顺序为:[[1,2,3], [2,1,3], [2,3,1], [1,3,2], [3,1,2], [3,2,1]]

上面的三种解法都是递归的,我们也可以使用迭代的方法来做。其实下面这个解法就上面解法的迭代写法,核心思路都是一样的,都是在现有的排列的基础上,每个空位插入一个数字,从而生成各种的全排列的情况,参见代码如下:

解法四:

class Solution {
public:
    vector<vector<int>> permute(vector<int>& nums) {
        vector<vector<int>> res{{}};
        for (int a : nums) {
            for (int k = res.size(); k > 0; --k) {
                vector<int> t = res.front();
                res.erase(res.begin());
                for (int i = 0; i <= t.size(); ++i) {
                    vector<int> one = t;
                    one.insert(one.begin() + i, a);
                    res.push_back(one);
                }
            }
        }
        return res;
    }
};

上述解法的最终生成顺序为:[[3,2,1], [2,3,1], [2,1,3], [3,1,2], [1,3,2], [1,2,3]]

下面这种解法就有些耍赖了,用了 STL 的内置函数 next_permutation(),专门就是用来返回下一个全排列,耳边又回响起了诸葛孔明的名言,我从未见过如此...投机取巧...的解法!

解法五:

class Solution {
public:
    vector<vector<int>> permute(vector<int>& nums) {
        vector<vector<int>> res;
        sort(nums.begin(), nums.end());
        res.push_back(nums);
        while (next_permutation(nums.begin(), nums.end())) {
            res.push_back(nums);
        }
        return res;
    }
};

上述解法的最终生成顺序为:[[1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1]]

Github 同步地址:

#46

类似题目:

Next Permutation

Permutations II

Permutation Sequence

Combinations

参考资料:

https://leetcode.com/problems/permutations/

https://leetcode.com/problems/permutations/discuss/18462/Share-my-three-different-solutions

https://leetcode.com/problems/permutations/discuss/18255/Share-my-short-iterative-JAVA-solution

https://leetcode.com/problems/permutations/discuss/18239/A-general-approach-to-backtracking-questions-in-Java-(Subsets-Permutations-Combination-Sum-Palindrome-Partioning)

LeetCode All in One 题目讲解汇总(持续更新中...)

(欢迎加入博主的知识星球,博主将及时答疑解惑,并分享刷题经验与总结,快快加入吧~)

知识星球 喜欢请点赞,疼爱请打赏❤️~.~

微信打赏

|

Venmo 打赏


---|---

[LeetCode] 10. Regular Expression Matching


请点击下方图片观看讲解视频
Click below image to watch YouTube Video
Video

Given an input string s and a pattern p, implement regular expression matching with support for '.' and '*' where:

  • '.' Matches any single character.​​​​
  • '*' Matches zero or more of the preceding element.

The matching should cover the entire input string (not partial).

Example 1:

**Input:** s = "aa", p = "a"
**Output:** false
**Explanation:** "a" does not match the entire string "aa".

Example 2:

**Input:** s = "aa", p = "a*"
**Output:** true
**Explanation:** '*' means zero or more of the preceding element, 'a'. Therefore, by repeating 'a' once, it becomes "aa".

Example 3:

**Input:** s = "ab", p = ".*"
**Output:** true
**Explanation:** ".*" means "zero or more (*) of any character (.)".

Constraints:

  • 1 <= s.length <= 20
  • 1 <= p.length <= 20
  • s contains only lowercase English letters.
  • p contains only lowercase English letters, '.', and '*'.
  • It is guaranteed for each appearance of the character '*', there will be a previous valid character to match.

这道求正则表达式匹配的题和那道 Wildcard Matching 的题很类似,不同点在于的意义不同,在之前那道题中,表示可以代替任意个数的字符,而这道题中的表示之前那个字符可以有0个,1个或是多个,就是说,字符串 ab,可以表示b或是 aaab,即a的个数任意,这道题的难度要相对之前那一道大一些,分的情况的要复杂一些,大概思路如下:

- 若p为空,再判断若s也为空,返回 true,反之返回 false。

- 若p的长度为1,再判断若s长度也为1,且相同或是p为 '.' 则返回 true,反之返回 false。

- 若p的第二个字符不为*,若此时s为空返回 false,否则判断首字符是否匹配,且从各自的第二个字符开始调用递归函数匹配。

- 若p的第二个字符为*,进行下列循环,条件是若s不为空且首字符匹配(包括 p[0] 为点),调用递归函数匹配s和去掉前两个字符的p(这样做的原因是假设此时的星号的作用是让前面的字符出现0次,验证是否匹配),若匹配返回 true,否则s去掉首字母(因为此时首字母匹配了,我们可以去掉s的首字母,而p由于星号的作用,可以有任意个首字母,所以不需要去掉),继续进行循环。

- 返回调用递归函数匹配s和去掉前两个字符的p的结果(这么做的原因是处理星号无法匹配的内容,比如 s="ab", p="ab",直接进入 while 循环后,我们发现 "ab" 和 "b" 不匹配,所以s变成 "b",那么此时跳出循环后,就到最后的 return 来比较 "b" 和 "b" 了,返回 true。再举个例子,比如 s="", p="a",由于s为空,不会进入任何的 if 和 while,只能到最后的 return 来比较了,返回 true,正确)。

解法一:

class Solution {
public:
    bool isMatch(string s, string p) {
        if (p.empty()) return s.empty();
        if (p.size() == 1) {
            return (s.size() == 1 && (s[0] == p[0] || p[0] == '.'));
        }
        if (p[1] != '*') {
            if (s.empty()) return false;
            return (s[0] == p[0] || p[0] == '.') && isMatch(s.substr(1), p.substr(1));
        }
        while (!s.empty() && (s[0] == p[0] || p[0] == '.')) {
            if (isMatch(s, p.substr(2))) return true;
            s = s.substr(1);
        }
        return isMatch(s, p.substr(2));
    }
};

上面的方法可以写的更加简洁一些,但是整个思路还是一样的,先来判断p是否为空,若为空则根据s的为空的情况返回结果。当p的第二个字符为号时,由于号前面的字符的个数可以任意,可以为0,那么我们先用递归来调用为0的情况,就是直接把这两个字符去掉再比较,或者当s不为空,且第一个字符和p的第一个字符相同时,再对去掉首字符的s和p调用递归,注意p不能去掉首字符,因为号前面的字符可以有无限个;如果第二个字符不为号,那么就老老实实的比较第一个字符,然后对后面的字符串调用递归,参见代码如下( 现在这种解法已经超时了 ):

解法二:

// Time Limit Exceeded
class Solution {
public:
    bool isMatch(string s, string p) {
        if (p.empty()) return s.empty();
        if (p.size() > 1 && p[1] == '*') {
            return isMatch(s, p.substr(2)) || (!s.empty() && (s[0] == p[0] || p[0] == '.') && isMatch(s.substr(1), p));
        } else {
            return !s.empty() && (s[0] == p[0] || p[0] == '.') && isMatch(s.substr(1), p.substr(1));
        }
    }
};

我们也可以用 DP 来解,定义一个二维的 DP 数组,其中 dp[i][j] 表示 s[0,i) 和 p[0,j) 是否 match,然后有下面三种情况(下面部分摘自这个帖子):

dp[i][j] = dp[i - 1][j - 1]  
if p[j - 1] != '*' && (s[i - 1] == p[j - 1] || p[j - 1] == '.');  

dp[i][j] = dp[i][j - 2]  
if p[j - 1] == '*' and the pattern repeats for 0 times;  

dp[i][j] = dp[i - 1][j] && (s[i - 1] == p[j - 2] || p[j - 2] == '.')  
if p[j - 1] == '*' and the pattern repeats for at least 1 times.

解法三:

class Solution {
public:
    bool isMatch(string s, string p) {
        int m = s.size(), n = p.size();
        vector<vector<bool>> dp(m + 1, vector<bool>(n + 1, false));
        dp[0][0] = true;
        for (int i = 0; i <= m; ++i) {
            for (int j = 1; j <= n; ++j) {
                if (j > 1 && p[j - 1] == '*') {
                    dp[i][j] = dp[i][j - 2] || (i > 0 && (s[i - 1] == p[j - 2] || p[j - 2] == '.') && dp[i - 1][j]);
                } else {
                    dp[i][j] = i > 0 && dp[i - 1][j - 1] && (s[i - 1] == p[j - 1] || p[j - 1] == '.');
                }
            }
        }
        return dp[m][n];
    }
};

GitHub 同步地址:

#10

类似题目:

Wildcard Matching

参考资料:

https://leetcode.com/problems/regular-expression-matching/

https://leetcode.com/problems/regular-expression-matching/discuss/5684/9-lines-16ms-c-dp-solutions-with-explanations

https://leetcode.com/problems/regular-expression-matching/discuss/5665/my-concise-recursive-and-dp-solutions-with-full-explanation-in-c

LeetCode All in One 题目讲解汇总(持续更新中...)

(欢迎加入博主的知识星球,博主将及时答疑解惑,并分享刷题经验与总结,快快加入吧~)

知识星球 喜欢请点赞,疼爱请打赏❤️~.~

微信打赏

|

Venmo 打赏


---|---

[LeetCode] 37. Sudoku Solver


请点击下方图片观看讲解视频
Click below image to watch YouTube Video
Video

Write a program to solve a Sudoku puzzle by filling the empty cells.

A sudoku solution must satisfy all of the following rules :

  1. Each of the digits 1-9 must occur exactly once in each row.
  2. Each of the digits 1-9 must occur exactly once in each column.
  3. Each of the digits 1-9 must occur exactly once in each of the 9 3x3 sub-boxes of the grid.

The '.' character indicates empty cells.

Example 1:

**Input:** board = [["5","3",".",".","7",".",".",".","."],["6",".",".","1","9","5",".",".","."],[".","9","8",".",".",".",".","6","."],["8",".",".",".","6",".",".",".","3"],["4",".",".","8",".","3",".",".","1"],["7",".",".",".","2",".",".",".","6"],[".","6",".",".",".",".","2","8","."],[".",".",".","4","1","9",".",".","5"],[".",".",".",".","8",".",".","7","9"]]
**Output:** [["5","3","4","6","7","8","9","1","2"],["6","7","2","1","9","5","3","4","8"],["1","9","8","3","4","2","5","6","7"],["8","5","9","7","6","1","4","2","3"],["4","2","6","8","5","3","7","9","1"],["7","1","3","9","2","4","8","5","6"],["9","6","1","5","3","7","2","8","4"],["2","8","7","4","1","9","6","3","5"],["3","4","5","2","8","6","1","7","9"]]
**Explanation:** The input board is shown above and the only valid solution is shown below:

Constraints:

  • board.length == 9
  • board[i].length == 9
  • board[i][j] is a digit or '.'.
  • It is guaranteed that the input board has only one solution.

这道求解数独的题是在之前那道 Valid Sudoku 的基础上的延伸,之前那道题让我们验证给定的数组是否为数独数组,这道让求解数独数组,跟此题类似的有 PermutationsCombinations N-Queens 等等,其中尤其是跟 N-Queens 的解题思路及其相似,对于每个需要填数字的格子带入1到9,每代入一个数字都判定其是否合法,如果合法就继续下一次递归,结束时把数字设回 '.',判断新加入的数字是否合法时,只需要判定当前数字是否合法,不需要判定这个数组是否为数独数组,因为之前加进的数字都是合法的,这样可以使程序更加高效一些,整体思路是这样的,但是实现起来可以有不同的形式。

一种实现形式是递归带上横纵坐标,由于是一行一行的填数字,且是从0行开始的,所以当i到达9的时候,说明所有的数字都成功的填入了,直接返回 ture。当j大于等于9时,当前行填完了,需要换到下一行继续填,则继续调用递归函数,横坐标带入 i+1。否则看若当前数字不为点,说明当前位置不需要填数字,则对右边的位置调用递归。若当前位置需要填数字,则应该尝试填入1到9内的所有数字,让c从1遍历到9,每当试着填入一个数字,都需要检验是否有冲突,使用另一个子函数 isValid 来检验是否合法,假如不合法,则跳过当前数字。若合法,则将当前位置赋值为这个数字,并对右边位置调用递归,若递归函数返回 true,则说明可以成功填充,直接返回 true。不行的话,需要重置状态,将当前位置恢复为点。若所有数字都尝试了,还是不行,则最终返回 false。检测当前数组是否合法的原理跟之前那道 Valid Sudoku 非常的相似,但更简单一些,因为这里只需要检测新加入的这个数字是否会跟其他位置引起冲突,分别检测新加入数字的行列和所在的小区间内是否有重复的数字即可,参见代码如下:

解法一:

class Solution {
public:
    void solveSudoku(vector<vector<char>>& board) {
        helper(board, 0, 0);
    }
    bool helper(vector<vector<char>>& board, int i, int j) {
        if (i == 9) return true;
        if (j >= 9) return helper(board, i + 1, 0);
        if (board[i][j] != '.') return helper(board, i, j + 1);
        for (char c = '1'; c <= '9'; ++c) {
            if (!isValid(board, i , j, c)) continue;
            board[i][j] = c;
            if (helper(board, i, j + 1)) return true;
            board[i][j] = '.';
        }
        return false;
    }
    bool isValid(vector<vector<char>>& board, int i, int j, char val) {
        for (int x = 0; x < 9; ++x) {
            if (board[x][j] == val) return false;
        }
        for (int y = 0; y < 9; ++y) {
            if (board[i][y] == val) return false;
        }
        int row = i - i % 3, col = j - j % 3;
        for (int x = 0; x < 3; ++x) {
            for (int y = 0; y < 3; ++y) {
                if (board[x + row][y + col] == val) return false;
            }
        }
        return true;
    }
};

还有另一种递归的写法,这里就不带横纵坐标参数进去,由于递归需要有 boolean 型的返回值,所以不能使用原函数。因为没有横纵坐标,所以每次遍历都需要从开头0的位置开始,这样无形中就有了大量的重复检测,导致这种解法虽然写法简洁一些,但击败率是没有上面的解法高的。这里的检测数组冲突的子函数写法也比上面简洁不少,只用了一个 for 循环,用来同时检测行列和小区间是否有冲突,注意正确的坐标转换即可,参见代码如下:

解法二:

class Solution {
public:
    void solveSudoku(vector<vector<char>>& board) {
        helper(board);
    }
    bool helper(vector<vector<char>>& board) {
        for (int i = 0; i < 9; ++i) {
            for (int j = 0; j < 9; ++j) {
                if (board[i][j] != '.') continue;
                for (char c = '1'; c <= '9'; ++c) {
                    if (!isValid(board, i, j, c)) continue;
                    board[i][j] = c;
                    if (helper(board)) return true;
                    board[i][j] = '.';
                }
                return false;
            }
        }
        return true;
    }
    bool isValid(vector<vector<char>>& board, int i, int j, char val) {
        for (int k = 0; k < 9; ++k) {
            if (board[k][j] != '.' && board[k][j] == val) return false;
            if (board[i][k] != '.' && board[i][k] == val) return false;
            int row = i / 3 * 3 + k / 3, col = j / 3 * 3 + k % 3;
            if (board[row][col] != '.' && board[row][col] == val) return false;
        }
        return true;
    }
};

Github 同步地址:

#37

类似题目:

Valid Sudoku

Unique Paths III

参考资料:

https://leetcode.com/problems/sudoku-solver/

https://leetcode.com/problems/sudoku-solver/discuss/15853/Simple-and-Clean-Solution-C%2B%2B

https://leetcode.com/problems/sudoku-solver/discuss/15752/Straight-Forward-Java-Solution-Using-Backtracking

LeetCode All in One 题目讲解汇总(持续更新中...)

(欢迎加入博主的知识星球,博主将及时答疑解惑,并分享刷题经验与总结,快快加入吧~)

知识星球 喜欢请点赞,疼爱请打赏❤️~.~

微信打赏

|

Venmo 打赏


---|---

[LeetCode] 17. Letter Combinations of a Phone Number


请点击下方图片观看讲解视频
Click below image to watch YouTube Video
Video

Given a string containing digits from 2-9 inclusive, return all possible letter combinations that the number could represent. Return the answer in any order.

A mapping of digits to letters (just like on the telephone buttons) is given below. Note that 1 does not map to any letters.

Example 1:

**Input:** digits = "23"
**Output:** ["ad","ae","af","bd","be","bf","cd","ce","cf"]

Example 2:

**Input:** digits = ""
**Output:** []

Example 3:

**Input:** digits = "2"
**Output:** ["a","b","c"]

Constraints:

  • 0 <= digits.length <= 4
  • digits[i] is a digit in the range ['2', '9'].

这道题让我们求电话号码的字母组合,即数字2到9中每个数字可以代表若干个字母,然后给一串数字,求出所有可能的组合,相类似的题目有 Path Sum IISubsets IIPermutationsPermutations IICombinationsCombination SumCombination Sum II 等等。这里可以用递归 Recursion 来解,需要建立一个字典,用来保存每个数字所代表的字符串,然后还需要一个变量 pos,记录当前生成的字符串的字符个数,实现套路和上述那些题十分类似。在递归函数中首先判断 pos,如果跟 digits 中数字的个数相等了,将当前的组合加入结果 res 中,然后返回。我们通过 digits 中的数字到 dict 中取出字符串,然后遍历这个取出的字符串,将每个字符都加到当前的组合后面,并调用递归函数即可,参见代码如下:

解法一:

class Solution {
public:
    vector<string> letterCombinations(string digits) {
        if (digits.empty()) return {};
        vector<string> res;
        vector<string> dict{"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
        dfs(digits, dict, 0, "", res);
        return res;
    }
    void dfs(string digits, vector<string>& dict, int pos, string cur, vector<string>& res) {
        if (pos == digits.size()) { res.push_back(cur); return; }
        string str = dict[digits[pos] - '0'];
        for (int i = 0; i < str.size(); ++i) {
            dfs(digits, dict, pos + 1, cur + str[i], res);
        }
    }
}; 

这道题也可以用迭代 Iterative 来解,在遍历 digits 中所有的数字时,先建立一个临时的字符串数组t,然后跟上面解法的操作一样,通过数字到 dict 中取出字符串 str,然后遍历取出字符串中的所有字符,再遍历当前结果 res 中的每一个字符串,将字符加到后面,并加入到临时字符串数组t中。取出的字符串 str 遍历完成后,将临时字符串数组赋值给结果 res,具体实现参见代码如下:

解法二:

class Solution {
public:
    vector<string> letterCombinations(string digits) {
        if (digits.empty()) return {};
        vector<string> res{""};
        vector<string> dict{"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
        for (int i = 0; i < digits.size(); ++i) {
            vector<string> t;
            string str = dict[digits[i] - '0'];
            for (int j = 0; j < str.size(); ++j) {
                for (string s : res) t.push_back(s + str[j]);
            }
            res = t;
        }
        return res;
    }
};

Github 同步地址:

#17

类似题目:

Generate Parentheses

Combination Sum

Binary Watch

参考资料:

https://leetcode.com/problems/letter-combinations-of-a-phone-number/

https://leetcode.com/problems/letter-combinations-of-a-phone-number/discuss/8109/My-recursive-solution-using-Java

https://leetcode.com/problems/letter-combinations-of-a-phone-number/discuss/8097/My-iterative-sollution-very-simple-under-15-lines

https://leetcode.com/problems/letter-combinations-of-a-phone-number/discuss/8207/Concise-15-line-Java-Iterative-Solution-very-Straight-Forward-with-Brief-Explanation

LeetCode All in One 题目讲解汇总(持续更新中...)

(欢迎加入博主的知识星球,博主将及时答疑解惑,并分享刷题经验与总结,快快加入吧~)

知识星球 喜欢请点赞,疼爱请打赏❤️~.~

微信打赏

|

Venmo 打赏


---|---

[LeetCode] 42. Trapping Rain Water


请点击下方图片观看讲解视频
Click below image to watch YouTube Video
Video

Given n non-negative integers representing an elevation map where the width of each bar is 1, compute how much water it can trap after raining.

Example 1:

Input: height = [0,1,0,2,1,0,1,3,2,1,2,1]
Output: 6
Explanation: The above elevation map (black section) is represented by array [0,1,0,2,1,0,1,3,2,1,2,1]. In this case, 6 units of rain water (blue section) are being trapped.

Example 2:

Input: height = [4,2,0,3,2,5]
Output: 9

Constraints:

  • n == height.length
  • 1 <= n <= 2 * 10^4
  • 0 <= height[i] <= 10^5

这道收集雨水的题跟之前的那道 Largest Rectangle in Histogram 有些类似,但是又不太一样,先来看一种方法,这种方法是基于动态规划 Dynamic Programming 的,维护一个一维的 dp 数组,这个 DP 算法需要遍历两遍数组,第一遍在 dp[i] 中存入i位置左边的最大值,然后开始第二遍遍历数组,第二次遍历时找右边最大值,然后和左边最大值比较取其中的较小值,然后跟当前值 A[i] 相比,如果大于当前值,则将差值存入结果,参见代码如下:

C++ 解法一:

class Solution {
public:
    int trap(vector<int>& height) {
        int res = 0, mx = 0, n = height.size();
        vector<int> dp(n);
        for (int i = 0; i < n; ++i) {
            dp[i] = mx;
            mx = max(mx, height[i]);
        }
        mx = 0;
        for (int i = n - 1; i >= 0; --i) {
            dp[i] = min(dp[i], mx);
            mx = max(mx, height[i]);
            if (dp[i] > height[i]) res += dp[i] - height[i];
        }
        return res;
    }
};

Java 解法一:

public class Solution {
    public int trap(int[] height) {
        int res = 0, mx = 0, n = height.length;
        int[] dp = new int[n];
        for (int i = 0; i < n; ++i) {
            dp[i] = mx;
            mx = Math.max(mx, height[i]);
        }
        mx = 0;
        for (int i = n - 1; i >= 0; --i) {
            dp[i] = Math.min(dp[i], mx);
            mx = Math.max(mx, height[i]);
            if (dp[i] - height[i] > 0) res += dp[i] - height[i];
        }
        return res;
    }
}

再看一种只需要遍历一次即可的解法,这个算法需要 left 和 right 两个指针分别指向数组的首尾位置,从两边向中间扫描,在当前两指针确定的范围内,先比较两头找出较小值,如果较小值是 left 指向的值,则从左向右扫描,如果较小值是 right 指向的值,则从右向左扫描,若遇到的值比当较小值小,则将差值存入结果,如遇到的值大,则重新确定新的窗口范围,以此类推直至 left 和 right 指针重合,参见代码如下:

C++ 解法二:

class Solution {
public:
    int trap(vector<int>& height) {
        int res = 0, left = 0, right = (int)height.size() - 1;
        while (left < right) {
            int mn = min(height[left], height[right]);
            if (mn == height[left]) {
                ++left;
                while (left < right && height[left] < mn) {
                    res += mn - height[left++];
                }
            } else {
                --right;
                while (left < right && height[right] < mn) {
                    res += mn - height[right--];
                }
            }
        }
        return res;
    }
};

Java 解法二:

public class Solution {
    public int trap(int[] height) {
        int res = 0, l = 0, r = height.length - 1;
        while (l < r) {
            int mn = Math.min(height[l], height[r]);
            if (height[l] == mn) {
                ++l;
                while (l < r && height[l] < mn) {
                    res += mn - height[l++];
                }
            } else {
                --r;
                while (l < r && height[r] < mn) {
                    res += mn - height[r--];
                }
            }
        }
        return res;
    }
}

我们可以对上面的解法进行进一步优化,使其更加简洁:

C++ 解法三:

class Solution {
public:
    int trap(vector<int>& height) {
        int left = 0, right = (int)height.size() - 1, level = 0, res = 0;
        while (left < right) {
            int lower = height[(height[left] < height[right]) ? left++ : right--];
            level = max(level, lower);
            res += level - lower;
        }
        return res;
    }
};

Java 解法三:

public class Solution {
    public int trap(int[] height) {
        int l = 0, r = height.length - 1, level = 0, res = 0;
        while (l < r) {
            int lower = height[(height[l] < height[r]) ? l++ : r--];
            level = Math.max(level, lower);
            res += level - lower;
        }
        return res;
    }
}

下面这种解法是用 stack 来做的,博主一开始都没有注意到这道题的 tag 还有 stack,所以以后在总结的时候还是要多多留意一下标签啊。其实用 stack 的方法博主感觉更容易理解,思路是,遍历高度,如果此时栈为空,或者当前高度小于等于栈顶高度,则把当前高度的坐标压入栈,注意这里不直接把高度压入栈,而是把坐标压入栈,这样方便在后来算水平距离。当遇到比栈顶高度大的时候,就说明有可能会有坑存在,可以装雨水。此时栈里至少有一个高度,如果只有一个的话,那么不能形成坑,直接跳过,如果多余一个的话,那么此时把栈顶元素取出来当作坑,新的栈顶元素就是左边界,当前高度是右边界,只要取二者较小的,减去坑的高度,长度就是右边界坐标减去左边界坐标再减1,二者相乘就是盛水量啦,参见代码如下:

C++ 解法四:

class Solution {
public:
    int trap(vector<int>& height) {
        stack<int> st;
        int i = 0, res = 0, n = height.size();
        while (i < n) {
            if (st.empty() || height[i] <= height[st.top()]) {
                st.push(i++);
            } else {
                int t = st.top(); st.pop();
                if (st.empty()) continue;
                res += (min(height[i], height[st.top()]) - height[t]) * (i - st.top() - 1);
            }
        }
        return res;
    }
};

Java 解法四:

class Solution {
    public int trap(int[] height) {
        Stack<Integer> s = new Stack<Integer>();
        int i = 0, n = height.length, res = 0;
        while (i < n) {
            if (s.isEmpty() || height[i] <= height[s.peek()]) {
                s.push(i++);
            } else {
                int t = s.pop();
                if (s.isEmpty()) continue;
                res += (Math.min(height[i], height[s.peek()]) - height[t]) * (i - s.peek() - 1);
            }
        }
        return res;
    }
}

Github 同步地址:

#42

类似题目:

Trapping Rain Water II

Container With Most Water

Product of Array Except Self

Pour Water

Maximum Value of an Ordered Triplet II

参考资料:

https://leetcode.com/problems/trapping-rain-water/

https://leetcode.com/problems/trapping-rain-water/discuss/17364/7-lines-C-C%2B%2B

https://leetcode.com/problems/trapping-rain-water/discuss/17414/A-stack-based-solution-for-reference-inspired-by-Histogram

https://leetcode.com/problems/trapping-rain-water/discuss/17357/Sharing-my-simple-c%2B%2B-code%3A-O(n)-time-O(1)-space

LeetCode All in One 题目讲解汇总(持续更新中...)

(欢迎加入博主的知识星球,博主将及时答疑解惑,并分享刷题经验与总结,快快加入吧~)

知识星球 喜欢请点赞,疼爱请打赏❤️~.~

微信打赏

|

Venmo 打赏


---|---

[LeetCode] 28. Find the Index of the First Occurrence in a String


请点击下方图片观看讲解视频
Click below image to watch YouTube Video
Video

Given two strings needle and haystack, return the index of the first occurrence of needle in haystack, or -1 if needle is not part of haystack.

Example 1:

**Input:** haystack = "sadbutsad", needle = "sad"
**Output:** 0
**Explanation:** "sad" occurs at index 0 and 6.
The first occurrence is at index 0, so we return 0.

Example 2:

**Input:** haystack = "leetcode", needle = "leeto"
**Output:** -1
**Explanation:** "leeto" did not occur in "leetcode", so we return -1.

Constraints:

  • 1 <= haystack.length, needle.length <= 104
  • haystack and needle consist of only lowercase English characters.

这道题让在一个字符串中找另一个字符串第一次出现的位置,那首先要做一些判断,如果子字符串为空,则返回0,如果子字符串长度大于母字符串长度,则返回 -1。然后开始遍历母字符串,这里并不需要遍历整个母字符串,而是遍历到剩下的长度和子字符串相等的位置即可,这样可以提高运算效率。然后对于每一个字符,都遍历一遍子字符串,一个一个字符的对应比较,如果对应位置有不等的,则跳出循环,如果一直都没有跳出循环,则说明子字符串出现了,则返回起始位置即可,代码如下:

class Solution {
public:
    int strStr(string haystack, string needle) {
        if (needle.empty()) return 0;
        int m = haystack.size(), n = needle.size();
        if (m < n) return -1;
        for (int i = 0; i <= m - n; ++i) {
            int j = 0;
            for (j = 0; j < n; ++j) {
                if (haystack[i + j] != needle[j]) break;
            }
            if (j == n) return i;
        }
        return -1;
    }
};

我们也可以写的更加简洁一些,开头直接套两个 for 循环,不写终止条件,然后判断假如j到达 needle 的末尾了,此时返回i;若此时 i+j 到达 haystack 的长度了,返回 -1;否则若当前对应的字符不匹配,直接跳出当前循环,参见代码如下:

解法二:

class Solution {
public:
    int strStr(string haystack, string needle) {
        for (int i = 0; ; ++i) {
            for (int j = 0; ; ++j) {
                if (j == needle.size()) return i;
                if (i + j == haystack.size()) return -1;
                if (needle[j] != haystack[i + j]) break;
            }
        }
        return -1;
    }
};

Github 同步地址:

#28

类似题目:

Shortest Palindrome

Repeated Substring Pattern

参考资料:

https://leetcode.com/problems/implement-strstr/

https://leetcode.com/problems/implement-strstr/discuss/12807/Elegant-Java-solution

https://leetcode.com/problems/implement-strstr/discuss/12956/C%2B%2B-Brute-Force-and-KMP

LeetCode All in One 题目讲解汇总(持续更新中...)

(欢迎加入博主的知识星球,博主将及时答疑解惑,并分享刷题经验与总结,快快加入吧~)

知识星球 喜欢请点赞,疼爱请打赏❤️~.~

微信打赏

|

Venmo 打赏


---|---

[LeetCode] 6. ZigZag Conversion


请点击下方图片观看讲解视频
Click below image to watch YouTube Video
Video

The string "PAYPALISHIRING" is written in a zigzag pattern on a given number of rows like this: (you may want to display this pattern in a fixed font for better legibility)

P   A   H   N
A P L S I I G
Y   I   R

And then read line by line: "PAHNAPLSIIGYIR"

Write the code that will take a string and make this conversion given a number of rows:

string convert(string s, int numRows);

Example 1:

**Input:** s = "PAYPALISHIRING", numRows = 3
**Output:** "PAHNAPLSIIGYIR"

Example 2:

**Input:** s = "PAYPALISHIRING", numRows = 4
**Output:** "PINALSIGYAHRPI"
**Explanation:**
P     I    N
A   L S  I G
Y A   H R
P     I

Example 3:

**Input:** s = "A", numRows = 1
**Output:** "A"

Constraints:

  • 1 <= s.length <= 1000
  • s consists of English letters (lower-case and upper-case), ',' and '.'.
  • 1 <= numRows <= 1000

这道题刚开始看了半天没看懂是咋样变换的,上网查了些资料,终于搞懂了,就是要把字符串摆成一个之字型的,比如有一个字符串 "0123456789ABCDEF",转为 zigzag 如下所示:

当 numRows = 2 时:

0 2 4 6 8 A C E

1 3 5 7 9 B D F

当 numRows = 3 时:

0 4 8 C

1 3 5 7 9 B D F

2 6 A E

当 numRows = 4 时:

0 6 C

1 5 7 B D

2 4 8 A E

3 9 F

可以发现,除了第一行和最后一行没有中间形成之字型的数字外,其他都有,而首尾两行中相邻两个元素的 index 之差跟行数是相关的,为 2nRows - 2, 根据这个特点,可以按顺序找到所有的黑色元素在原字符串的位置,将它们按顺序加到新字符串里面。对于红色元素出现的位置(Github 上可能无法正常显示颜色,请参见博客园上的帖子)也是有规律的,每个红色元素的位置为 j + 2numRows-2 - 2*i, 其中,j为同一行前一个黑色数字的在原字符串中的位置 index,i为当前行数。 比如当 n = 4 中的那个红色5,它的位置为 1 + 2 x 4-2 - 2 x 1 = 5,为原字符串的正确位置。知道了所有黑色元素和红色元素位置的正确算法,就可以一次性的把它们按顺序都加到新的字符串里面。代码如下:

解法一:

class Solution {
public:
    string convert(string s, int numRows) {
        if (numRows <= 1) return s;
        string res;
        int size = 2 * numRows - 2, n = s.size();
        for (int i = 0; i < numRows; ++i) {
            for (int j = i; j < n; j += size) {
                res += s[j];
                int pos = j + size - 2 * i;
                if (i != 0 && i != numRows - 1 && pos < n) res += s[pos];
            }
        }
        return res;
    }
};

若上面解法中的规律不是很好想的话,我们也可以用下面这种更直接的方法来做,建立一个大小为 numRows 的字符串数组,为的就是把之字形的数组整个存进去,然后再把每一行的字符拼接起来,就是想要的结果了。顺序就是按列进行遍历,首先前 numRows 个字符就是按顺序存在每行的第一个位置,然后就是 ‘之’ 字形的连接位置了,可以发现其实都是在行数区间 [1, numRows-2] 内,只要按顺序去取字符就可以了,最后把每行都拼接起来即为所求,参见代码如下:

解法二:

class Solution {
public:
    string convert(string s, int numRows) {
        if (numRows <= 1) return s;
        string res;
        int i = 0, n = s.size();
        vector<string> vec(numRows);
        while (i < n) {
            for (int pos = 0; pos < numRows && i < n; ++pos) {
                vec[pos] += s[i++];
            }
            for (int pos = numRows - 2; pos >= 1 && i < n; --pos) {
                vec[pos] += s[i++];
            }
        }
        for (auto &a : vec) res += a;
        return res;
    }
};

Github 同步地址:

#6

类似题目:

Zigzag Iterator

Binary Tree Zigzag Level Order Traversal

参考资料:

https://leetcode.com/problems/zigzag-conversion/

https://www.cnblogs.com/springfor/p/3889414.html

https://leetcode.com/problems/zigzag-conversion/discuss/3403/Easy-to-understand-Java-solution

https://leetcode.com/problems/zigzag-conversion/discuss/3417/A-10-lines-one-pass-o(n)-time-o(1)-space-accepted-solution-with-detailed-explantation

LeetCode All in One 题目讲解汇总(持续更新中...)

(欢迎加入博主的知识星球,博主将及时答疑解惑,并分享刷题经验与总结,快快加入吧~)

知识星球 喜欢请点赞,疼爱请打赏❤️~.~

微信打赏

|

Venmo 打赏


---|---

[LeetCode] 32. Longest Valid Parentheses


请点击下方图片观看讲解视频
Click below image to watch YouTube Video
Video

Given a string containing just the characters '(' and ')', return the length of the longest valid (well-formed) parentheses substring.

Example 1:

**Input:** s = "(()"
**Output:** 2
**Explanation:** The longest valid parentheses substring is "()".

Example 2:

**Input:** s = ")()())"
**Output:** 4
**Explanation:** The longest valid parentheses substring is "()()".

Example 3:

**Input:** s = ""
**Output:** 0

Constraints:

  • 0 <= s.length <= 3 * 10^4
  • s[i] is '(', or ')'.

这道求最长有效括号比之前那道 Valid Parentheses 难度要大一些,这里还是借助栈来求解,需要定义个 start 变量来记录合法括号串的起始位置,遍历字符串,如果遇到左括号,则将当前下标压入栈,如果遇到右括号,如果当前栈为空,则将下一个坐标位置记录到 start,如果栈不为空,则将栈顶元素取出,此时若栈为空,则更新结果和 i - start + 1 中的较大值,否则更新结果和 i - st.top() 中的较大值,有的童鞋可能会好奇,为啥一个有加一,另一个没有呢?其实这就是一个坐标的问题,start 指向的是有效括号的起始位置,那么算长度的时候就要加一,比如对于 "()" 来说,此时 start=0,i=1,那么长度就是 i-start+1 = 2。而栈顶元素表示的是有效括号的前一个位置,就比如说对于 "(()" 来说,此时栈顶元素是第一个左括号的位置,即0,而 i=2,由于第一个左括号不在有效括号长度中,所以就是直接 i-st.top()=2,参见代码如下:

解法一:

class Solution {
public:
    int longestValidParentheses(string s) {
        int res = 0, start = 0, n = s.size();
        stack<int> st;
        for (int i = 0; i < n; ++i) {
            if (s[i] == '(') st.push(i);
            else if (s[i] == ')') {
                if (st.empty()) {
                    start = i + 1;
                } else {
                    st.pop();
                    res = st.empty() ? max(res, i - start + 1) : max(res, i - st.top());
                }
            }
        }
        return res;
    }
};

还有一种利用动态规划 Dynamic Programming 的解法,可参见网友喜刷刷的博客。这里使用一个一维 dp 数组,其中 dp[i] 表示以 s[i-1] 结尾的最长有效括号长度(注意这里没有对应 s[i],是为了避免取 dp[i-1] 时越界从而让 dp 数组的长度加了1),s[i-1] 此时必须是有效括号的一部分,那么只要 dp[i] 为正数的话,说明 s[i-1] 一定是右括号,因为有效括号必须是闭合的。当括号有重合时,比如 "(())",会出现多个右括号相连,此时更新最外边的右括号的 dp[i] 时是需要前一个右括号的值 dp[i-1],因为假如 dp[i-1] 为正数,说明此位置往前 dp[i-1] 个字符组成的子串都是合法的子串,需要再看前面一个位置,假如是左括号,说明在 dp[i-1] 的基础上又增加了一个合法的括号,所以长度加上2。但此时还可能出现的情况是,前面的左括号前面还有合法括号,比如 "()(())",此时更新最后面的右括号的时候,知道第二个右括号的 dp 值是2,那么最后一个右括号的 dp 值不仅是第二个括号的 dp 值再加2,还可以连到第一个右括号的 dp 值,整个最长的有效括号长度是6。所以在更新当前右括号的 dp 值时,首先要计算出第一个右括号的位置,通过 i-3-dp[i-1] 来获得,由于这里定义的 dp[i] 对应的是字符 s[i-1],所以需要再加1,变成 j = i-2-dp[i-1],这样若当前字符 s[i-1] 是左括号,或者j小于0(说明没有对应的左括号),或者 s[j] 是右括号,此时将 dp[i] 重置为0,否则就用 dp[i-1] + 2 + dp[j] 来更新 dp[i]。这里由于进行了 padding,可能对应关系会比较晕,大家可以自行带个例子一步一步执行,应该是不难理解的,参见代码如下: **
**

解法二:

class Solution {
public:
    int longestValidParentheses(string s) {
        int res = 0, n = s.size();
        vector<int> dp(n + 1);
        for (int i = 1; i <= n; ++i) {
            int j = i - 2 - dp[i - 1];
            if (s[i - 1] == '(' || j < 0 || s[j] == ')') {
                dp[i] = 0;
            } else {
                dp[i] = dp[i - 1] + 2 + dp[j];
                res = max(res, dp[i]);
            }
        }
        return res;
    }
};

此题还有一种不用额外空间的解法,使用了两个变量 left 和 right,分别用来记录到当前位置时左括号和右括号的出现次数,当遇到左括号时,left 自增1,右括号时 right 自增1。对于最长有效的括号的子串,一定是左括号等于右括号的情况,此时就可以更新结果 res 了,一旦右括号数量超过左括号数量了,说明当前位置不能组成合法括号子串,left 和 right 重置为0。但是对于这种情况 "(()" 时,在遍历结束时左右子括号数都不相等,此时没法更新结果 res,但其实正确答案是2,怎么处理这种情况呢?答案是再反向遍历一遍,采取类似的机制,稍有不同的是此时若 left 大于 right 了,则重置0,这样就可以 cover 所有的情况了,参见代码如下:

解法三:

class Solution {
public:
    int longestValidParentheses(string s) {
        int res = 0, left = 0, right = 0, n = s.size();
        for (int i = 0; i < n; ++i) {
            (s[i] == '(') ? ++left : ++right;
            if (left == right) res = max(res, 2 * right);
            else if (right > left) left = right = 0;
        }
        left = right = 0;
        for (int i = n - 1; i >= 0; --i) {
            (s[i] == '(') ? ++left : ++right;
            if (left == right) res = max(res, 2 * left);
            else if (left > right) left = right = 0;
        }
        return res;
    }
};

Github 同步地址:

#32

类似题目:

Remove Invalid Parentheses

Different Ways to Add Parentheses

Generate Parentheses

Valid Parentheses

参考资料:

https://leetcode.com/problems/longest-valid-parentheses/

https://bangbingsyb.blogspot.com/2014/11/leetcode-longest-valid-parentheses.html

https://leetcode.com/problems/longest-valid-parentheses/discuss/14126/My-O(n)-solution-using-a-stack

https://leetcode.com/problems/longest-valid-parentheses/discuss/14133/My-DP-O(n)-solution-without-using-stack

LeetCode All in One 题目讲解汇总(持续更新中...)

(欢迎加入博主的知识星球,博主将及时答疑解惑,并分享刷题经验与总结,快快加入吧~)

知识星球 喜欢请点赞,疼爱请打赏❤️~.~

微信打赏

|

Venmo 打赏


---|---

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.